~lattis/muon

3 2

[PATCH 0/2] python: impl `install_sources()` and import tests

Details
Message ID
<D1EOOT908W27.2G4D0B439AZGA@gmail.com>
DKIM signature
pass
Download raw message
Hi,

Patch 1/2 implements `install_sources()`. The implementation is mostly a
copy-paste from `install_data()`. Those kwargs not implemented are
marked with a TODO comment.

Patch 2/2 imports the test suite for Python module from the meson
project. Not all of the tests run currently (due to missing impl of
`dependency()` and `extension_module()`) - these are commented out for
now.

Thanks!

Seedo Paul (2):
  mod/python: implement `install_sources`
  tests: import python tests from meson

 src/functions/modules/python.c                | 125 ++++++++++++++++++
 tests/project/meson.build                     |  14 ++
 .../project/python/1 basic/gluon/__init__.py  |   0
 .../project/python/1 basic/gluon/gluonator.py |   2 +
 tests/project/python/1 basic/meson.build      |  30 +++++
 tests/project/python/1 basic/prog.py          |   8 ++
 .../project/python/1 basic/subdir/meson.build |   4 +
 .../project/python/1 basic/subdir/subprog.py  |  11 ++
 .../meson.build                               |  10 ++
 .../module.c                                  |  17 +++
 .../project/python/2 extmodule/blaster.py.in  |  11 ++
 .../python/2 extmodule/ext/meson.build        |  15 +++
 .../python/2 extmodule/ext/nested/meson.build |  32 +++++
 .../python/2 extmodule/ext/tachyon_module.c   |  59 +++++++++
 .../2 extmodule/ext/wrongdir/meson.build      |  12 ++
 tests/project/python/2 extmodule/meson.build  |  57 ++++++++
 .../python/2 extmodule/meson_options.txt      |   1 +
 .../python/2 extmodule/subinst/printer.py     |   3 +
 .../2 extmodule/subinst/submod/printer.py     |   3 +
 tests/project/python/2 extmodule/test.json    |  15 +++
 tests/project/python/3 cython/cytest.py       |  19 +++
 .../python/3 cython/libdir/cstorer.pxd        |   9 ++
 .../python/3 cython/libdir/meson.build        |  11 ++
 tests/project/python/3 cython/libdir/storer.c |  24 ++++
 tests/project/python/3 cython/libdir/storer.h |   8 ++
 .../project/python/3 cython/libdir/storer.pyx |  16 +++
 tests/project/python/3 cython/meson.build     |  25 ++++
 .../blaster.py                                |  33 +++++
 .../ext/lib/meson-tachyonlib.c                |   8 ++
 .../ext/lib/meson-tachyonlib.h                |   6 +
 .../ext/lib/meson.build                       |   4 +
 .../ext/meson.build                           |   6 +
 .../ext/tachyon_module.c                      |  51 +++++++
 .../meson.build                               |  45 +++++++
 tests/project/python/5 modules kwarg/a.py     |   0
 .../python/5 modules kwarg/meson.build        |  19 +++
 .../project/python/5 modules kwarg/test.json  |   5 +
 .../python/6 failing subproject/meson.build   |   5 +
 .../subprojects/bar/meson.build               |   4 +
 .../project/python/7 install path/meson.build |  25 ++++
 .../7 install path/structured/alpha/one.py    |   0
 .../7 install path/structured/alpha/three.py  |   0
 .../7 install path/structured/alpha/two.py    |   0
 .../7 install path/structured/beta/one.py     |   0
 .../7 install path/structured/meson.build     |   9 ++
 .../python/7 install path/structured/one.py   |   0
 .../python/7 install path/structured/two.py   |   0
 .../python/7 install path/target/meson.build  |   3 +
 tests/project/python/7 install path/test.json |  18 +++
 tests/project/python/7 install path/test.py   |   0
 .../8 different python versions/blaster.py    |  14 ++
 .../ext/meson.build                           |   6 +
 .../ext/tachyon_module.c                      |  59 +++++++++
 .../8 different python versions/meson.build   |  34 +++++
 .../meson_options.txt                         |   4 +
 .../8 different python versions/test.json     |  13 ++
 .../python/9 extmodule limited api/limited.c  |  19 +++
 .../9 extmodule limited api/meson.build       |  16 +++
 .../9 extmodule limited api/not_limited.c     |  59 +++++++++
 .../python/9 extmodule limited api/test.json  |   8 ++
 60 files changed, 1014 insertions(+)
 create mode 100644 tests/project/python/1 basic/gluon/__init__.py
 create mode 100644 tests/project/python/1 basic/gluon/gluonator.py
 create mode 100644 tests/project/python/1 basic/meson.build
 create mode 100755 tests/project/python/1 basic/prog.py
 create mode 100644 tests/project/python/1 basic/subdir/meson.build
 create mode 100755 tests/project/python/1 basic/subdir/subprog.py
 create mode 100644 tests/project/python/10 extmodule limited api disabled/meson.build
 create mode 100644 tests/project/python/10 extmodule limited api disabled/module.c
 create mode 100755 tests/project/python/2 extmodule/blaster.py.in
 create mode 100644 tests/project/python/2 extmodule/ext/meson.build
 create mode 100644 tests/project/python/2 extmodule/ext/nested/meson.build
 create mode 100644 tests/project/python/2 extmodule/ext/tachyon_module.c
 create mode 100644 tests/project/python/2 extmodule/ext/wrongdir/meson.build
 create mode 100644 tests/project/python/2 extmodule/meson.build
 create mode 100644 tests/project/python/2 extmodule/meson_options.txt
 create mode 100755 tests/project/python/2 extmodule/subinst/printer.py
 create mode 100755 tests/project/python/2 extmodule/subinst/submod/printer.py
 create mode 100644 tests/project/python/2 extmodule/test.json
 create mode 100755 tests/project/python/3 cython/cytest.py
 create mode 100644 tests/project/python/3 cython/libdir/cstorer.pxd
 create mode 100644 tests/project/python/3 cython/libdir/meson.build
 create mode 100644 tests/project/python/3 cython/libdir/storer.c
 create mode 100644 tests/project/python/3 cython/libdir/storer.h
 create mode 100644 tests/project/python/3 cython/libdir/storer.pyx
 create mode 100644 tests/project/python/3 cython/meson.build
 create mode 100644 tests/project/python/4 custom target depends extmodule/blaster.py
 create mode 100644 tests/project/python/4 custom target depends extmodule/ext/lib/meson-tachyonlib.c
 create mode 100644 tests/project/python/4 custom target depends extmodule/ext/lib/meson-tachyonlib.h
 create mode 100644 tests/project/python/4 custom target depends extmodule/ext/lib/meson.build
 create mode 100644 tests/project/python/4 custom target depends extmodule/ext/meson.build
 create mode 100644 tests/project/python/4 custom target depends extmodule/ext/tachyon_module.c
 create mode 100644 tests/project/python/4 custom target depends extmodule/meson.build
 create mode 100644 tests/project/python/5 modules kwarg/a.py
 create mode 100644 tests/project/python/5 modules kwarg/meson.build
 create mode 100644 tests/project/python/5 modules kwarg/test.json
 create mode 100644 tests/project/python/6 failing subproject/meson.build
 create mode 100644 tests/project/python/6 failing subproject/subprojects/bar/meson.build
 create mode 100644 tests/project/python/7 install path/meson.build
 create mode 100644 tests/project/python/7 install path/structured/alpha/one.py
 create mode 100644 tests/project/python/7 install path/structured/alpha/three.py
 create mode 100644 tests/project/python/7 install path/structured/alpha/two.py
 create mode 100644 tests/project/python/7 install path/structured/beta/one.py
 create mode 100644 tests/project/python/7 install path/structured/meson.build
 create mode 100644 tests/project/python/7 install path/structured/one.py
 create mode 100644 tests/project/python/7 install path/structured/two.py
 create mode 100644 tests/project/python/7 install path/target/meson.build
 create mode 100644 tests/project/python/7 install path/test.json
 create mode 100644 tests/project/python/7 install path/test.py
 create mode 100755 tests/project/python/8 different python versions/blaster.py
 create mode 100644 tests/project/python/8 different python versions/ext/meson.build
 create mode 100644 tests/project/python/8 different python versions/ext/tachyon_module.c
 create mode 100644 tests/project/python/8 different python versions/meson.build
 create mode 100644 tests/project/python/8 different python versions/meson_options.txt
 create mode 100644 tests/project/python/8 different python versions/test.json
 create mode 100644 tests/project/python/9 extmodule limited api/limited.c
 create mode 100644 tests/project/python/9 extmodule limited api/meson.build
 create mode 100644 tests/project/python/9 extmodule limited api/not_limited.c
 create mode 100644 tests/project/python/9 extmodule limited api/test.json


base-commit: 3a38f252b80488c28548fe9248246138df281c68
-- 
2.45.1

[PATCH 1/2] mod/python: implement `install_sources`

Details
Message ID
<D1EOOT909SXU.1OH0KV6S9FQVI@gmail.com>
In-Reply-To
<D1EOOT908W27.2G4D0B439AZGA@gmail.com> (view parent)
DKIM signature
pass
Download raw message
Patch: +125 -0
---
 src/functions/modules/python.c | 125 +++++++++++++++++++++++++++++++++
 1 file changed, 125 insertions(+)

diff --git a/src/functions/modules/python.c b/src/functions/modules/python.c
index c5024ddb..a0768f52 100644
--- a/src/functions/modules/python.c
+++ b/src/functions/modules/python.c
@@ -395,6 +395,130 @@ func_python_installation_get_install_dir(struct workspace *wk, obj self, obj *re
	return get_install_dir(wk, self, pure, subdir, res);
}

struct py_install_data_rename_ctx {
	obj rename;
	obj mode;
	obj dest;
	uint32_t i;
	uint32_t node;
};

static enum iteration_result
py_install_data_rename_iter(struct workspace *wk, void *_ctx, obj val)
{
	struct py_install_data_rename_ctx *ctx = _ctx;

	obj src = *get_obj_file(wk, val);
	obj dest;

	obj rename;
	obj_array_index(wk, ctx->rename, ctx->i, &rename);

	SBUF(d);
	path_join(wk, &d, get_cstr(wk, ctx->dest), get_cstr(wk, rename));

	dest = sbuf_into_str(wk, &d);

	push_install_target(wk, src, dest, ctx->mode);

	++ctx->i;
	return ir_cont;
}

static bool
func_python_installation_install_sources(struct workspace *wk, obj self, obj *res)
{
	struct args_norm an[] = { { TYPE_TAG_GLOB | tc_file | tc_string }, ARG_TYPE_NULL };
	enum kwargs {
		kw_follow_symlinks,
		kw_install_dir,
		kw_install_mode,
		kw_install_tag,
		kw_rename,
		kw_sources,
		kw_preserve_path,
		kw_pure,
		kw_subdir,
	};

	struct args_kw akw[] = {
		[kw_follow_symlinks] = { "follow_symlinks", obj_bool }, // TODO
		[kw_install_dir] = { "install_dir", obj_string },
		[kw_install_mode] = { "install_mode", tc_install_mode_kw },
		[kw_install_tag] = { "install_tag", obj_string }, // TODO
		[kw_rename] = { "rename", TYPE_TAG_LISTIFY | obj_string },
		[kw_sources] = { "sources", TYPE_TAG_LISTIFY | tc_file | tc_string },
		[kw_preserve_path] = { "preserve_path", obj_bool },
		[kw_pure] = { "pure", obj_bool },
		[kw_subdir] = { "subdir", obj_string },
		0
	};

	if (!pop_args(wk, an, akw)) {
		return false;
	}

	if (akw[kw_rename].set && akw[kw_preserve_path].set) {
		vm_error(wk, "rename keyword conflicts with preserve_path");
		return false;
	}

	struct obj_python_installation *py = get_obj_python_installation(wk, self);
	bool pure = py->pure;
	if (akw[kw_pure].set) {
		pure = get_obj_bool(wk, akw[kw_pure].val);
	}

	const char *subdir = NULL;
	if (akw[kw_subdir].set) {
		subdir = get_cstr(wk, akw[kw_subdir].val);
	}

	obj install_dir;
	if (akw[kw_install_dir].set) {
		install_dir = akw[kw_install_dir].val;
	} else {
		get_install_dir(wk, self, pure, subdir, &install_dir);
	}

	obj sources = an[0].val;
	uint32_t err_node = an[0].node;

	if (akw[kw_sources].set) {
		obj_array_extend_nodup(wk, sources, akw[kw_sources].val);
		err_node = akw[kw_sources].node;
	}

	if (akw[kw_rename].set) {
		if (get_obj_array(wk, akw[kw_rename].val)->len !=
		    get_obj_array(wk, sources)->len) {
			vm_error(wk, "number of elements in rename != number of sources");
			return false;
		}

		struct py_install_data_rename_ctx ctx = {
			.node = err_node,
			.mode = akw[kw_install_mode].val,
			.rename = akw[kw_rename].val,
			.dest = install_dir,
		};

		obj coerced;
		if (!coerce_files(wk, err_node, sources, &coerced)) {
			return false;
		}

		return obj_array_foreach(wk, coerced, &ctx, py_install_data_rename_iter);
	}

	bool preserve_path =
		akw[kw_preserve_path].set
		&& get_obj_bool(wk, akw[kw_preserve_path].val);

	return push_install_targets(wk, err_node, sources, install_dir,
		akw[kw_install_mode].val, preserve_path);
}

static obj
python_self_transform(struct workspace *wk, obj self)
{
@@ -428,6 +552,7 @@ struct func_impl impl_tbl_python_installation[] = {
	{ "get_variable", func_python_installation_get_var, tc_string },
	{ "has_path", func_python_installation_has_path, tc_bool },
	{ "has_variable", func_python_installation_has_var, tc_bool },
	{ "install_sources", func_python_installation_install_sources },
	{ "language_version", func_python_installation_language_version, tc_string },
	{ NULL, NULL },
};
-- 
2.45.1

[PATCH 2/2] tests: import python tests from meson

Details
Message ID
<D1EOOT90A3PO.TVK50RG1N4NB@gmail.com>
In-Reply-To
<D1EOOT908W27.2G4D0B439AZGA@gmail.com> (view parent)
DKIM signature
pass
Download raw message
Tests that fail due to missing implementation are commented out.
---
 tests/project/meson.build                     | 14 +++++
 .../project/python/1 basic/gluon/__init__.py  |  0
 .../project/python/1 basic/gluon/gluonator.py |  2 +
 tests/project/python/1 basic/meson.build      | 30 ++++++++++
 tests/project/python/1 basic/prog.py          |  8 +++
 .../project/python/1 basic/subdir/meson.build |  4 ++
 .../project/python/1 basic/subdir/subprog.py  | 11 ++++
 .../meson.build                               | 10 ++++
 .../module.c                                  | 17 ++++++
 .../project/python/2 extmodule/blaster.py.in  | 11 ++++
 .../python/2 extmodule/ext/meson.build        | 15 +++++
 .../python/2 extmodule/ext/nested/meson.build | 32 ++++++++++
 .../python/2 extmodule/ext/tachyon_module.c   | 59 +++++++++++++++++++
 .../2 extmodule/ext/wrongdir/meson.build      | 12 ++++
 tests/project/python/2 extmodule/meson.build  | 57 ++++++++++++++++++
 .../python/2 extmodule/meson_options.txt      |  1 +
 .../python/2 extmodule/subinst/printer.py     |  3 +
 .../2 extmodule/subinst/submod/printer.py     |  3 +
 tests/project/python/2 extmodule/test.json    | 15 +++++
 tests/project/python/3 cython/cytest.py       | 19 ++++++
 .../python/3 cython/libdir/cstorer.pxd        |  9 +++
 .../python/3 cython/libdir/meson.build        | 11 ++++
 tests/project/python/3 cython/libdir/storer.c | 24 ++++++++
 tests/project/python/3 cython/libdir/storer.h |  8 +++
 .../project/python/3 cython/libdir/storer.pyx | 16 +++++
 tests/project/python/3 cython/meson.build     | 25 ++++++++
 .../blaster.py                                | 33 +++++++++++
 .../ext/lib/meson-tachyonlib.c                |  8 +++
 .../ext/lib/meson-tachyonlib.h                |  6 ++
 .../ext/lib/meson.build                       |  4 ++
 .../ext/meson.build                           |  6 ++
 .../ext/tachyon_module.c                      | 51 ++++++++++++++++
 .../meson.build                               | 45 ++++++++++++++
 tests/project/python/5 modules kwarg/a.py     |  0
 .../python/5 modules kwarg/meson.build        | 19 ++++++
 .../project/python/5 modules kwarg/test.json  |  5 ++
 .../python/6 failing subproject/meson.build   |  5 ++
 .../subprojects/bar/meson.build               |  4 ++
 .../project/python/7 install path/meson.build | 25 ++++++++
 .../7 install path/structured/alpha/one.py    |  0
 .../7 install path/structured/alpha/three.py  |  0
 .../7 install path/structured/alpha/two.py    |  0
 .../7 install path/structured/beta/one.py     |  0
 .../7 install path/structured/meson.build     |  9 +++
 .../python/7 install path/structured/one.py   |  0
 .../python/7 install path/structured/two.py   |  0
 .../python/7 install path/target/meson.build  |  3 +
 tests/project/python/7 install path/test.json | 18 ++++++
 tests/project/python/7 install path/test.py   |  0
 .../8 different python versions/blaster.py    | 14 +++++
 .../ext/meson.build                           |  6 ++
 .../ext/tachyon_module.c                      | 59 +++++++++++++++++++
 .../8 different python versions/meson.build   | 34 +++++++++++
 .../meson_options.txt                         |  4 ++
 .../8 different python versions/test.json     | 13 ++++
 .../python/9 extmodule limited api/limited.c  | 19 ++++++
 .../9 extmodule limited api/meson.build       | 16 +++++
 .../9 extmodule limited api/not_limited.c     | 59 +++++++++++++++++++
 .../python/9 extmodule limited api/test.json  |  8 +++
 59 files changed, 889 insertions(+)
 create mode 100644 tests/project/python/1 basic/gluon/__init__.py
 create mode 100644 tests/project/python/1 basic/gluon/gluonator.py
 create mode 100644 tests/project/python/1 basic/meson.build
 create mode 100755 tests/project/python/1 basic/prog.py
 create mode 100644 tests/project/python/1 basic/subdir/meson.build
 create mode 100755 tests/project/python/1 basic/subdir/subprog.py
 create mode 100644 tests/project/python/10 extmodule limited api disabled/meson.build
 create mode 100644 tests/project/python/10 extmodule limited api disabled/module.c
 create mode 100755 tests/project/python/2 extmodule/blaster.py.in
 create mode 100644 tests/project/python/2 extmodule/ext/meson.build
 create mode 100644 tests/project/python/2 extmodule/ext/nested/meson.build
 create mode 100644 tests/project/python/2 extmodule/ext/tachyon_module.c
 create mode 100644 tests/project/python/2 extmodule/ext/wrongdir/meson.build
 create mode 100644 tests/project/python/2 extmodule/meson.build
 create mode 100644 tests/project/python/2 extmodule/meson_options.txt
 create mode 100755 tests/project/python/2 extmodule/subinst/printer.py
 create mode 100755 tests/project/python/2 extmodule/subinst/submod/printer.py
 create mode 100644 tests/project/python/2 extmodule/test.json
 create mode 100755 tests/project/python/3 cython/cytest.py
 create mode 100644 tests/project/python/3 cython/libdir/cstorer.pxd
 create mode 100644 tests/project/python/3 cython/libdir/meson.build
 create mode 100644 tests/project/python/3 cython/libdir/storer.c
 create mode 100644 tests/project/python/3 cython/libdir/storer.h
 create mode 100644 tests/project/python/3 cython/libdir/storer.pyx
 create mode 100644 tests/project/python/3 cython/meson.build
 create mode 100644 tests/project/python/4 custom target depends extmodule/blaster.py
 create mode 100644 tests/project/python/4 custom target depends extmodule/ext/lib/meson-tachyonlib.c
 create mode 100644 tests/project/python/4 custom target depends extmodule/ext/lib/meson-tachyonlib.h
 create mode 100644 tests/project/python/4 custom target depends extmodule/ext/lib/meson.build
 create mode 100644 tests/project/python/4 custom target depends extmodule/ext/meson.build
 create mode 100644 tests/project/python/4 custom target depends extmodule/ext/tachyon_module.c
 create mode 100644 tests/project/python/4 custom target depends extmodule/meson.build
 create mode 100644 tests/project/python/5 modules kwarg/a.py
 create mode 100644 tests/project/python/5 modules kwarg/meson.build
 create mode 100644 tests/project/python/5 modules kwarg/test.json
 create mode 100644 tests/project/python/6 failing subproject/meson.build
 create mode 100644 tests/project/python/6 failing subproject/subprojects/bar/meson.build
 create mode 100644 tests/project/python/7 install path/meson.build
 create mode 100644 tests/project/python/7 install path/structured/alpha/one.py
 create mode 100644 tests/project/python/7 install path/structured/alpha/three.py
 create mode 100644 tests/project/python/7 install path/structured/alpha/two.py
 create mode 100644 tests/project/python/7 install path/structured/beta/one.py
 create mode 100644 tests/project/python/7 install path/structured/meson.build
 create mode 100644 tests/project/python/7 install path/structured/one.py
 create mode 100644 tests/project/python/7 install path/structured/two.py
 create mode 100644 tests/project/python/7 install path/target/meson.build
 create mode 100644 tests/project/python/7 install path/test.json
 create mode 100644 tests/project/python/7 install path/test.py
 create mode 100755 tests/project/python/8 different python versions/blaster.py
 create mode 100644 tests/project/python/8 different python versions/ext/meson.build
 create mode 100644 tests/project/python/8 different python versions/ext/tachyon_module.c
 create mode 100644 tests/project/python/8 different python versions/meson.build
 create mode 100644 tests/project/python/8 different python versions/meson_options.txt
 create mode 100644 tests/project/python/8 different python versions/test.json
 create mode 100644 tests/project/python/9 extmodule limited api/limited.c
 create mode 100644 tests/project/python/9 extmodule limited api/meson.build
 create mode 100644 tests/project/python/9 extmodule limited api/not_limited.c
 create mode 100644 tests/project/python/9 extmodule limited api/test.json

diff --git a/tests/project/meson.build b/tests/project/meson.build
index 5a2e95f0..19825bef 100644
--- a/tests/project/meson.build
+++ b/tests/project/meson.build
@@ -291,6 +291,20 @@ tests = [
     ['common/260 declare_dependency objects'],
     # ['common/261 testcase clause'],
 
+    # python
+    ['python/1 basic', ['python']],
+    # The following need the currently unimplemented `dependency()`
+    # ['python/2 extmodule', ['python']],
+    # ['python/3 cython', ['python']],
+    # ['python/4 custom target depends extmodule', ['python']],
+    # ['python/8 different python versions', ['python']],
+    ['python/5 modules kwarg', ['python']],
+    ['python/6 failing subproject', ['python', 'failing']],
+    ['python/7 install path', ['python']],
+    # These need the currently unimplemented `extension_module()`
+    # ['python/9 extmodule limited api', ['python']],
+    # ['python/10 extmodule limited api disabled', ['python']],
+
     # unit
     ['unit/65 alias target', ['python']],
 
diff --git a/tests/project/python/1 basic/gluon/__init__.py b/tests/project/python/1 basic/gluon/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/project/python/1 basic/gluon/gluonator.py b/tests/project/python/1 basic/gluon/gluonator.py
new file mode 100644
index 00000000..b53d6ded
--- /dev/null
+++ b/tests/project/python/1 basic/gluon/gluonator.py	
@@ -0,0 +1,2 @@
+def gluoninate():
+    return 42
diff --git a/tests/project/python/1 basic/meson.build b/tests/project/python/1 basic/meson.build
new file mode 100644
index 00000000..481a8817
--- /dev/null
+++ b/tests/project/python/1 basic/meson.build	
@@ -0,0 +1,30 @@
+project('python sample')
+
+py_mod = import('python')
+py = py_mod.find_installation('python3')
+
+py_version = py.language_version()
+if py_version.version_compare('< 3.2')
+  error('MESON_SKIP_TEST python 3 required for tests')
+endif
+
+py_full_version = py.version()
+message(f'Using python version: @py_full_version@')
+
+py_purelib = py.get_path('purelib')
+if not (py_purelib.endswith('site-packages') or py_purelib.endswith('dist-packages'))
+  error('Python3 purelib path seems invalid? ' + py_purelib)
+endif
+message('Python purelib path:', py_purelib)
+
+# could be 'lib64' or 'Lib' on some systems
+py_platlib = py.get_path('platlib')
+if not (py_platlib.endswith('site-packages') or py_platlib.endswith('dist-packages'))
+  error('Python3 platlib path seems invalid? ' + py_platlib)
+endif
+
+main = files('prog.py')
+
+test('toplevel', py, args : main)
+
+subdir('subdir')
diff --git a/tests/project/python/1 basic/prog.py b/tests/project/python/1 basic/prog.py
new file mode 100755
index 00000000..720fdb18
--- /dev/null
+++ b/tests/project/python/1 basic/prog.py	
@@ -0,0 +1,8 @@
+#!/usr/bin/env python3
+
+from gluon import gluonator
+
+print('Running mainprog from root dir.')
+
+if gluonator.gluoninate() != 42:
+    raise ValueError("!= 42")
diff --git a/tests/project/python/1 basic/subdir/meson.build b/tests/project/python/1 basic/subdir/meson.build
new file mode 100644
index 00000000..66957c10
--- /dev/null
+++ b/tests/project/python/1 basic/subdir/meson.build	
@@ -0,0 +1,4 @@
+test('subdir',
+  py,
+  args : files('subprog.py'),
+  env : 'PYTHONPATH=' + meson.source_root())
diff --git a/tests/project/python/1 basic/subdir/subprog.py b/tests/project/python/1 basic/subdir/subprog.py
new file mode 100755
index 00000000..54178e5f
--- /dev/null
+++ b/tests/project/python/1 basic/subdir/subprog.py	
@@ -0,0 +1,11 @@
+#!/usr/bin/env python3
+
+# In order to run this program, PYTHONPATH must be set to
+# point to source root.
+
+from gluon import gluonator
+
+print('Running mainprog from subdir.')
+
+if gluonator.gluoninate() != 42:
+    raise ValueError("!= 42")
diff --git a/tests/project/python/10 extmodule limited api disabled/meson.build b/tests/project/python/10 extmodule limited api disabled/meson.build
new file mode 100644
index 00000000..42cd6186
--- /dev/null
+++ b/tests/project/python/10 extmodule limited api disabled/meson.build	
@@ -0,0 +1,10 @@
+project('Python limited api disabled', 'c',
+  default_options : ['buildtype=release', 'werror=true', 'python.allow_limited_api=false'])
+
+py_mod = import('python')
+py = py_mod.find_installation()
+
+module = py.extension_module('my_module',
+  'module.c',
+  limited_api: '3.7',
+)
diff --git a/tests/project/python/10 extmodule limited api disabled/module.c b/tests/project/python/10 extmodule limited api disabled/module.c
new file mode 100644
index 00000000..a5d3a87a
--- /dev/null
+++ b/tests/project/python/10 extmodule limited api disabled/module.c	
@@ -0,0 +1,17 @@
+#include <Python.h>
+
+#if defined(Py_LIMITED_API)
+#error "Py_LIMITED_API's definition by Meson should have been disabled."
+#endif
+
+static struct PyModuleDef my_module = {
+   PyModuleDef_HEAD_INIT,
+   "my_module",
+   NULL,
+   -1,
+   NULL
+};
+
+PyMODINIT_FUNC PyInit_my_module(void) {
+    return PyModule_Create(&my_module);
+}
diff --git a/tests/project/python/2 extmodule/blaster.py.in b/tests/project/python/2 extmodule/blaster.py.in
new file mode 100755
index 00000000..c93026f4
--- /dev/null
+++ b/tests/project/python/2 extmodule/blaster.py.in	
@@ -0,0 +1,11 @@
+#!/usr/bin/env python3
+
+import @tachyon_module@ as tachyon
+
+result = tachyon.phaserize('shoot')
+
+if not isinstance(result, int):
+    raise SystemExit('Returned result not an integer.')
+
+if result != 1:
+    raise SystemExit('Returned result {} is not 1.'.format(result))
diff --git a/tests/project/python/2 extmodule/ext/meson.build b/tests/project/python/2 extmodule/ext/meson.build
new file mode 100644
index 00000000..0fba9f58
--- /dev/null
+++ b/tests/project/python/2 extmodule/ext/meson.build	
@@ -0,0 +1,15 @@
+pylib = py.extension_module('tachyon',
+  'tachyon_module.c',
+  c_args: '-DMESON_MODULENAME="tachyon"',
+  install: true,
+)
+
+pylib2 = py2.extension_module('tachyon',
+  'tachyon_module.c',
+  c_args: '-DMESON_MODULENAME="tachyon"',
+  install: true,
+)
+
+subdir('nested')
+subdir('wrongdir')
+pypathdir = meson.current_build_dir()
diff --git a/tests/project/python/2 extmodule/ext/nested/meson.build b/tests/project/python/2 extmodule/ext/nested/meson.build
new file mode 100644
index 00000000..4a7ec767
--- /dev/null
+++ b/tests/project/python/2 extmodule/ext/nested/meson.build	
@@ -0,0 +1,32 @@
+py.extension_module('tachyon',
+  '../tachyon_module.c',
+  c_args: '-DMESON_MODULENAME="nested.tachyon"',
+  install: true,
+  subdir: 'nested'
+)
+py.install_sources(
+  configure_file(
+    input: '../../blaster.py.in',
+    output: 'blaster.py',
+    configuration: {'tachyon_module': 'nested.tachyon'}
+  ),
+  pure: false,
+  subdir: 'nested',
+)
+
+
+py2.extension_module('tachyon',
+  '../tachyon_module.c',
+  c_args: '-DMESON_MODULENAME="nested.tachyon"',
+  install: true,
+  subdir: 'nested'
+)
+py2.install_sources(
+  configure_file(
+    input: '../../blaster.py.in',
+    output: 'blaster.py',
+    configuration: {'tachyon_module': 'nested.tachyon'}
+  ),
+  pure: false,
+  subdir: 'nested',
+)
diff --git a/tests/project/python/2 extmodule/ext/tachyon_module.c b/tests/project/python/2 extmodule/ext/tachyon_module.c
new file mode 100644
index 00000000..68eda538
--- /dev/null
+++ b/tests/project/python/2 extmodule/ext/tachyon_module.c	
@@ -0,0 +1,59 @@
+/*
+  Copyright 2018 The Meson development team
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+*/
+
+/* A very simple Python extension module. */
+
+#include <Python.h>
+#include <string.h>
+
+static PyObject* phaserize(PyObject *self, PyObject *args) {
+    const char *message;
+    int result;
+
+    if(!PyArg_ParseTuple(args, "s", &message))
+        return NULL;
+
+    result = strcmp(message, "shoot") ? 0 : 1;
+#if PY_VERSION_HEX < 0x03000000
+    return PyInt_FromLong(result);
+#else
+    return PyLong_FromLong(result);
+#endif
+}
+
+static PyMethodDef TachyonMethods[] = {
+    {"phaserize",  phaserize, METH_VARARGS,
+     "Shoot tachyon cannons."},
+    {NULL, NULL, 0, NULL}
+};
+
+#if PY_VERSION_HEX < 0x03000000
+PyMODINIT_FUNC inittachyon(void) {
+    Py_InitModule("tachyon", TachyonMethods);
+}
+#else
+static struct PyModuleDef tachyonmodule = {
+   PyModuleDef_HEAD_INIT,
+   "tachyon",
+   NULL,
+   -1,
+   TachyonMethods
+};
+
+PyMODINIT_FUNC PyInit_tachyon(void) {
+    return PyModule_Create(&tachyonmodule);
+}
+#endif
diff --git a/tests/project/python/2 extmodule/ext/wrongdir/meson.build b/tests/project/python/2 extmodule/ext/wrongdir/meson.build
new file mode 100644
index 00000000..79b13eb7
--- /dev/null
+++ b/tests/project/python/2 extmodule/ext/wrongdir/meson.build	
@@ -0,0 +1,12 @@
+py.extension_module('tachyon',
+  '../tachyon_module.c',
+  c_args: '-DMESON_MODULENAME="tachyon"',
+  install: true,
+  install_dir: get_option('libdir')
+)
+py2.extension_module('tachyon',
+  '../tachyon_module.c',
+  c_args: '-DMESON_MODULENAME="tachyon"',
+  install: true,
+  install_dir: get_option('libdir')
+)
diff --git a/tests/project/python/2 extmodule/meson.build b/tests/project/python/2 extmodule/meson.build
new file mode 100644
index 00000000..65f9177c
--- /dev/null
+++ b/tests/project/python/2 extmodule/meson.build	
@@ -0,0 +1,57 @@
+project('Python extension module', 'c',
+  default_options : ['buildtype=release', 'werror=true', 'python.bytecompile=-1'])
+# Because Windows Python ships only with optimized libs,
+# we must build this project the same way.
+
+if meson.backend() != 'ninja'
+  error('MESON_SKIP_TEST: Ninja backend required')
+endif
+
+
+py_mod = import('python')
+py = py_mod.find_installation()
+py2 = py_mod.find_installation('python2', required: get_option('python2'), disabler: true)
+py_dep = py.dependency(required: false)
+
+if not py_dep.found()
+  error('MESON_SKIP_TEST: Python libraries not found.')
+endif
+
+subdir('ext')
+
+blaster = configure_file(
+  input: 'blaster.py.in',
+  output: 'blaster.py',
+  configuration: {'tachyon_module': 'tachyon'}
+)
+
+test('extmod',
+  py,
+  args : blaster,
+  env : ['PYTHONPATH=' + pypathdir])
+
+py.install_sources(blaster, pure: false)
+py.install_sources(blaster, subdir: 'pure')
+install_subdir('subinst', install_dir: py.get_install_dir(pure: false))
+
+py2.install_sources(blaster, pure: false)
+py2.install_sources(blaster, subdir: 'pure')
+install_subdir('subinst', install_dir: py2.get_install_dir(pure: false))
+
+
+py3_pkg_dep = dependency('python3', method: 'pkg-config', required : false)
+if py3_pkg_dep.found()
+  py3_dep_majver = py3_pkg_dep.version().split('.')
+  py3_dep_majver = py3_dep_majver[0] + '.' + py3_dep_majver[1]
+  message(f'got two pythons: pkg-config is @py3_dep_majver@, and module is', py.language_version())
+  if py3_dep_majver != py.language_version()
+    message('skipped python3 pkg-config test because the default python3 is different from Meson\'s')
+  else
+    python_lib_dir = py3_pkg_dep.get_pkgconfig_variable('libdir')
+
+    # Check we can apply a version constraint
+    dependency('python3', version: '>=@0@'.format(py_dep.version()))
+  endif
+else
+  message('Skipped python3 pkg-config test because it was not found')
+endif
diff --git a/tests/project/python/2 extmodule/meson_options.txt b/tests/project/python/2 extmodule/meson_options.txt
new file mode 100644
index 00000000..76d3b678
--- /dev/null
+++ b/tests/project/python/2 extmodule/meson_options.txt	
@@ -0,0 +1 @@
+option('python2', type: 'feature', value: 'disabled')
diff --git a/tests/project/python/2 extmodule/subinst/printer.py b/tests/project/python/2 extmodule/subinst/printer.py
new file mode 100755
index 00000000..8bc528df
--- /dev/null
+++ b/tests/project/python/2 extmodule/subinst/printer.py	
@@ -0,0 +1,3 @@
+#!/usr/bin/env python3
+
+print('subinst')
diff --git a/tests/project/python/2 extmodule/subinst/submod/printer.py b/tests/project/python/2 extmodule/subinst/submod/printer.py
new file mode 100755
index 00000000..2a4a61bc
--- /dev/null
+++ b/tests/project/python/2 extmodule/subinst/submod/printer.py	
@@ -0,0 +1,3 @@
+#!/usr/bin/env python3
+
+print('subinst.submod')
diff --git a/tests/project/python/2 extmodule/test.json b/tests/project/python/2 extmodule/test.json
new file mode 100644
index 00000000..fcb3975a
--- /dev/null
+++ b/tests/project/python/2 extmodule/test.json	
@@ -0,0 +1,15 @@
+{
+  "installed": [
+    { "type": "python_file", "file": "usr/@PYTHON_PLATLIB@/blaster.py" },
+    { "type": "python_lib",  "file": "usr/@PYTHON_PLATLIB@/tachyon" },
+    { "type": "py_implib",   "file": "usr/@PYTHON_PLATLIB@/tachyon" },
+    { "type": "python_file", "file": "usr/@PYTHON_PURELIB@/pure/blaster.py" },
+    { "type": "python_file", "file": "usr/@PYTHON_PLATLIB@/nested/blaster.py" },
+    { "type": "python_lib",  "file": "usr/@PYTHON_PLATLIB@/nested/tachyon" },
+    { "type": "py_implib",   "file": "usr/@PYTHON_PLATLIB@/nested/tachyon" },
+    { "type": "python_file", "file": "usr/@PYTHON_PLATLIB@/subinst/printer.py" },
+    { "type": "python_file", "file": "usr/@PYTHON_PLATLIB@/subinst/submod/printer.py" },
+    { "type": "python_lib",  "file": "usr/lib/tachyon" },
+    { "type": "py_implib",   "file": "usr/lib/tachyon" }
+  ]
+}
diff --git a/tests/project/python/3 cython/cytest.py b/tests/project/python/3 cython/cytest.py
new file mode 100755
index 00000000..c08ffeed
--- /dev/null
+++ b/tests/project/python/3 cython/cytest.py	
@@ -0,0 +1,19 @@
+#!/usr/bin/env python3
+
+from storer import Storer
+
+s = Storer()
+
+if s.get_value() != 0:
+    raise SystemExit('Initial value incorrect.')
+
+s.set_value(42)
+
+if s.get_value() != 42:
+    raise SystemExit('Setting value failed.')
+
+try:
+    s.set_value('not a number')
+    raise SystemExit('Using wrong argument type did not fail.')
+except TypeError:
+    pass
diff --git a/tests/project/python/3 cython/libdir/cstorer.pxd b/tests/project/python/3 cython/libdir/cstorer.pxd
new file mode 100644
index 00000000..7b730fc7
--- /dev/null
+++ b/tests/project/python/3 cython/libdir/cstorer.pxd	
@@ -0,0 +1,9 @@
+
+cdef extern from "storer.h":
+    ctypedef struct Storer:
+        pass
+
+    Storer* storer_new();
+    void storer_destroy(Storer *s);
+    int storer_get_value(Storer *s);
+    void storer_set_value(Storer *s, int v);
diff --git a/tests/project/python/3 cython/libdir/meson.build b/tests/project/python/3 cython/libdir/meson.build
new file mode 100644
index 00000000..d148b00a
--- /dev/null
+++ b/tests/project/python/3 cython/libdir/meson.build	
@@ -0,0 +1,11 @@
+pyx_c = custom_target('storer_pyx',
+  output : 'storer_pyx.c',
+  input : 'storer.pyx',
+  command : [cython, '@INPUT@', '-o', '@OUTPUT@', '-3'],
+)
+
+slib = py3.extension_module('storer',
+  'storer.c', pyx_c,
+)
+
+pydir = meson.current_build_dir()
diff --git a/tests/project/python/3 cython/libdir/storer.c b/tests/project/python/3 cython/libdir/storer.c
new file mode 100644
index 00000000..0199bb85
--- /dev/null
+++ b/tests/project/python/3 cython/libdir/storer.c	
@@ -0,0 +1,24 @@
+#include"storer.h"
+#include<stdlib.h>
+
+struct _Storer {
+    int value;
+};
+
+Storer* storer_new() {
+    Storer *s = malloc(sizeof(struct _Storer));
+    s->value = 0;
+    return s;
+}
+
+void storer_destroy(Storer *s) {
+    free(s);
+}
+
+int storer_get_value(Storer *s) {
+    return s->value;
+}
+
+void storer_set_value(Storer *s, int v) {
+    s->value = v;
+}
diff --git a/tests/project/python/3 cython/libdir/storer.h b/tests/project/python/3 cython/libdir/storer.h
new file mode 100644
index 00000000..4f719171
--- /dev/null
+++ b/tests/project/python/3 cython/libdir/storer.h	
@@ -0,0 +1,8 @@
+#pragma once
+
+typedef struct _Storer Storer;
+
+Storer* storer_new();
+void storer_destroy(Storer *s);
+int storer_get_value(Storer *s);
+void storer_set_value(Storer *s, int v);
diff --git a/tests/project/python/3 cython/libdir/storer.pyx b/tests/project/python/3 cython/libdir/storer.pyx
new file mode 100644
index 00000000..ed551dc5
--- /dev/null
+++ b/tests/project/python/3 cython/libdir/storer.pyx	
@@ -0,0 +1,16 @@
+cimport cstorer
+
+cdef class Storer:
+    cdef cstorer.Storer* _c_storer
+
+    def __cinit__(self):
+        self._c_storer = cstorer.storer_new()
+
+    def __dealloc__(self):
+        cstorer.storer_destroy(self._c_storer)
+
+    cpdef int get_value(self):
+        return cstorer.storer_get_value(self._c_storer)
+
+    cpdef set_value(self, int value):
+        cstorer.storer_set_value(self._c_storer, value)
diff --git a/tests/project/python/3 cython/meson.build b/tests/project/python/3 cython/meson.build
new file mode 100644
index 00000000..8ff8d515
--- /dev/null
+++ b/tests/project/python/3 cython/meson.build	
@@ -0,0 +1,25 @@
+project('cython', 'c',
+  default_options : ['warning_level=3', 'buildtype=release']
+)
+if meson.backend() != 'ninja'
+  error('MESON_SKIP_TEST: Ninja backend required')
+endif
+
+cython = find_program('cython', required : false)
+if not cython.found()
+  error('MESON_SKIP_TEST: Cython3 not found.')
+endif
+
+py3 = import('python').find_installation(pure: false)
+py3_dep = py3.dependency(required: false)
+if not py3_dep.found()
+  error('MESON_SKIP_TEST: Python library not found.')
+endif
+
+subdir('libdir')
+
+test('cython tester',
+  py3,
+  args : files('cytest.py'),
+  env : ['PYTHONPATH=' + pydir]
+)
diff --git a/tests/project/python/4 custom target depends extmodule/blaster.py b/tests/project/python/4 custom target depends extmodule/blaster.py
new file mode 100644
index 00000000..65b6493d
--- /dev/null
+++ b/tests/project/python/4 custom target depends extmodule/blaster.py	
@@ -0,0 +1,33 @@
+#!/usr/bin/env python3
+
+import os
+import sys
+import argparse
+
+from pathlib import Path
+
+filedir = Path(os.path.dirname(__file__)).resolve()
+if list(filedir.glob('ext/*tachyon*')):
+    sys.path.insert(0, (filedir / 'ext').as_posix())
+
+if hasattr(os, 'add_dll_directory'):
+    os.add_dll_directory(filedir / 'ext' / 'lib')
+
+import tachyon
+
+parser = argparse.ArgumentParser()
+parser.add_argument('-o', dest='output', default=None)
+
+options = parser.parse_args(sys.argv[1:])
+
+result = tachyon.phaserize('shoot')
+
+if options.output:
+    with open(options.output, 'w') as f:
+        f.write('success')
+
+if not isinstance(result, int):
+    raise SystemExit('Returned result not an integer.')
+
+if result != 1:
+    raise SystemExit(f'Returned result {result} is not 1.')
diff --git a/tests/project/python/4 custom target depends extmodule/ext/lib/meson-tachyonlib.c b/tests/project/python/4 custom target depends extmodule/ext/lib/meson-tachyonlib.c
new file mode 100644
index 00000000..aeff2965
--- /dev/null
+++ b/tests/project/python/4 custom target depends extmodule/ext/lib/meson-tachyonlib.c	
@@ -0,0 +1,8 @@
+#ifdef _MSC_VER
+__declspec(dllexport)
+#endif
+const char*
+tachyon_phaser_command (void)
+{
+    return "shoot";
+}
diff --git a/tests/project/python/4 custom target depends extmodule/ext/lib/meson-tachyonlib.h b/tests/project/python/4 custom target depends extmodule/ext/lib/meson-tachyonlib.h
new file mode 100644
index 00000000..dca71d31
--- /dev/null
+++ b/tests/project/python/4 custom target depends extmodule/ext/lib/meson-tachyonlib.h	
@@ -0,0 +1,6 @@
+#pragma once
+
+#ifdef _MSC_VER
+__declspec(dllimport)
+#endif
+const char* tachyon_phaser_command (void);
diff --git a/tests/project/python/4 custom target depends extmodule/ext/lib/meson.build b/tests/project/python/4 custom target depends extmodule/ext/lib/meson.build
new file mode 100644
index 00000000..b1f89384
--- /dev/null
+++ b/tests/project/python/4 custom target depends extmodule/ext/lib/meson.build	
@@ -0,0 +1,4 @@
+libtachyon = shared_library('tachyonlib', 'meson-tachyonlib.c')
+
+libtachyon_dep = declare_dependency(link_with : libtachyon,
+                                    include_directories : include_directories('.'))
diff --git a/tests/project/python/4 custom target depends extmodule/ext/meson.build b/tests/project/python/4 custom target depends extmodule/ext/meson.build
new file mode 100644
index 00000000..1bb275d4
--- /dev/null
+++ b/tests/project/python/4 custom target depends extmodule/ext/meson.build	
@@ -0,0 +1,6 @@
+subdir('lib')
+
+pylib = py3.extension_module('tachyon',
+  'tachyon_module.c',
+  dependencies : [libtachyon_dep, py3_dep],
+)
diff --git a/tests/project/python/4 custom target depends extmodule/ext/tachyon_module.c b/tests/project/python/4 custom target depends extmodule/ext/tachyon_module.c
new file mode 100644
index 00000000..b48032bb
--- /dev/null
+++ b/tests/project/python/4 custom target depends extmodule/ext/tachyon_module.c	
@@ -0,0 +1,51 @@
+/*
+  Copyright 2016 The Meson development team
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+*/
+
+/* A very simple Python extension module. */
+
+#include <Python.h>
+#include <string.h>
+
+#include "meson-tachyonlib.h"
+
+static PyObject* phaserize(PyObject *self, PyObject *args) {
+    const char *message;
+    int result;
+
+    if(!PyArg_ParseTuple(args, "s", &message))
+        return NULL;
+
+    result = strcmp(message, tachyon_phaser_command()) ? 0 : 1;
+    return PyLong_FromLong(result);
+}
+
+static PyMethodDef TachyonMethods[] = {
+    {"phaserize",  phaserize, METH_VARARGS,
+     "Shoot tachyon cannons."},
+    {NULL, NULL, 0, NULL}
+};
+
+static struct PyModuleDef tachyonmodule = {
+   PyModuleDef_HEAD_INIT,
+   "tachyon",
+   NULL,
+   -1,
+   TachyonMethods
+};
+
+PyMODINIT_FUNC PyInit_tachyon(void) {
+    return PyModule_Create(&tachyonmodule);
+}
diff --git a/tests/project/python/4 custom target depends extmodule/meson.build b/tests/project/python/4 custom target depends extmodule/meson.build
new file mode 100644
index 00000000..d8a62ed2
--- /dev/null
+++ b/tests/project/python/4 custom target depends extmodule/meson.build	
@@ -0,0 +1,45 @@
+project('Python extension module', 'c',
+  default_options : ['buildtype=release'])
+# Because Windows Python ships only with optimized libs,
+# we must build this project the same way.
+
+if meson.backend() != 'ninja'
+  error('MESON_SKIP_TEST: Ninja backend required')
+endif
+
+py_mod = import('python')
+py3 = py_mod.find_installation()
+py3_dep = py3.dependency(required : false)
+cc = meson.get_compiler('c')
+
+if not py3_dep.found()
+  error('MESON_SKIP_TEST: Python3 libraries not found, skipping test.')
+endif
+
+# Copy to the builddir so that blaster.py can find the built tachyon module
+# FIXME: We should automatically detect this case and append the correct paths
+# to PYTHONLIBDIR
+blaster_py = configure_file(input : 'blaster.py',
+                            output : 'blaster.py',
+                            copy : true)
+
+check_exists = '''
+import os, sys
+with open(sys.argv[1], 'rb') as f:
+  assert(f.read() == b'success')
+'''
+
+message('Detected Python version: ' + py3_dep.version())
+if py3_dep.version().version_compare('>=3.8') and cc.get_id() == 'msvc' and cc.version().version_compare('<=19.00.24215.1')
+  error('MESON_SKIP_TEST: Python modules do not work with Python 3.8 and VS2015 or earlier.')
+endif
+subdir('ext')
+
+out_txt = custom_target('tachyon flux',
+  input : blaster_py,
+  output : 'out.txt',
+  command : [py3, '@INPUT@', '-o', '@OUTPUT@'],
+  depends : pylib,
+  build_by_default: true)
+
+test('flux', py3, args : ['-c', check_exists, out_txt])
diff --git a/tests/project/python/5 modules kwarg/a.py b/tests/project/python/5 modules kwarg/a.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/project/python/5 modules kwarg/meson.build b/tests/project/python/5 modules kwarg/meson.build
new file mode 100644
index 00000000..6e745ba6
--- /dev/null
+++ b/tests/project/python/5 modules kwarg/meson.build	
@@ -0,0 +1,19 @@
+project('python kwarg',
+    default_options: [
+        'python.bytecompile=-1',
+        'python.purelibdir=/pure',
+    ]
+)
+
+py = import('python')
+prog_python = py.find_installation('python3', modules : ['os', 'sys', 're'], pure: true)
+assert(prog_python.found() == true, 'python not found when should be')
+
+# In meson 1.2 - 1.3.2, there was a bug when a python installation
+# with a different version did not have a module, and we try to install
+# something with another python version...
+py.find_installation('python3.7', modules: ['notamodule'], required: false)
+prog_python.install_sources('a.py')
+
+prog_python = py.find_installation('python3', modules : ['thisbetternotexistmod'], required : false)
+assert(prog_python.found() == false, 'python not found but reported as found')
diff --git a/tests/project/python/5 modules kwarg/test.json b/tests/project/python/5 modules kwarg/test.json
new file mode 100644
index 00000000..cf874f10
--- /dev/null
+++ b/tests/project/python/5 modules kwarg/test.json	
@@ -0,0 +1,5 @@
+{
+    "installed": [
+        { "type": "python_file", "file": "pure/a.py"}
+    ]
+}
\ No newline at end of file
diff --git a/tests/project/python/6 failing subproject/meson.build b/tests/project/python/6 failing subproject/meson.build
new file mode 100644
index 00000000..cc33a1c7
--- /dev/null
+++ b/tests/project/python/6 failing subproject/meson.build	
@@ -0,0 +1,5 @@
+project('foo', 'cpp')
+
+# Regression test for https://github.com/mesonbuild/meson/issues/9038
+dependency('bar', required: false, allow_fallback: true)
+python = import('python').find_installation('python3').dependency()
diff --git a/tests/project/python/6 failing subproject/subprojects/bar/meson.build b/tests/project/python/6 failing subproject/subprojects/bar/meson.build
new file mode 100644
index 00000000..a5534ced
--- /dev/null
+++ b/tests/project/python/6 failing subproject/subprojects/bar/meson.build	
@@ -0,0 +1,4 @@
+project('bar', 'cpp')
+
+python = import('python').find_installation('python3')
+dependency('nonexistent-dependency')
diff --git a/tests/project/python/7 install path/meson.build b/tests/project/python/7 install path/meson.build
new file mode 100644
index 00000000..b9e185a4
--- /dev/null
+++ b/tests/project/python/7 install path/meson.build	
@@ -0,0 +1,25 @@
+project('install path',
+  default_options: [
+    'python.bytecompile=-1',
+    'python.purelibdir=pure',
+    'python.platlibdir=plat',
+  ]
+)
+
+py = import('python').find_installation()
+py.install_sources('test.py')
+py.install_sources('test.py', pure: false)
+install_data('test.py', install_dir: py.get_install_dir() / 'data')
+install_data('test.py', install_dir: py.get_install_dir(pure: false) / 'data')
+
+py_plat = import('python').find_installation(pure: false)
+py_plat.install_sources('test.py', subdir: 'kw')
+py_plat.install_sources('test.py', pure: true, subdir: 'kwrevert')
+install_data('test.py', install_dir: py_plat.get_install_dir() / 'kw/data')
+install_data('test.py', install_dir: py_plat.get_install_dir(pure: true) / 'kwrevert/data')
+
+if get_option('backend') == 'none'
+    subdir('target')
+endif
+
+subdir('structured')
diff --git a/tests/project/python/7 install path/structured/alpha/one.py b/tests/project/python/7 install path/structured/alpha/one.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/project/python/7 install path/structured/alpha/three.py b/tests/project/python/7 install path/structured/alpha/three.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/project/python/7 install path/structured/alpha/two.py b/tests/project/python/7 install path/structured/alpha/two.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/project/python/7 install path/structured/beta/one.py b/tests/project/python/7 install path/structured/beta/one.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/project/python/7 install path/structured/meson.build b/tests/project/python/7 install path/structured/meson.build
new file mode 100644
index 00000000..6c855879
--- /dev/null
+++ b/tests/project/python/7 install path/structured/meson.build	
@@ -0,0 +1,9 @@
+py.install_sources(
+  'one.py',
+  'two.py',
+  'alpha/one.py',
+  'alpha/two.py',
+  'alpha/three.py',
+  'beta/one.py',
+  preserve_path: true,
+)
diff --git a/tests/project/python/7 install path/structured/one.py b/tests/project/python/7 install path/structured/one.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/project/python/7 install path/structured/two.py b/tests/project/python/7 install path/structured/two.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/project/python/7 install path/target/meson.build b/tests/project/python/7 install path/target/meson.build
new file mode 100644
index 00000000..178ae022
--- /dev/null
+++ b/tests/project/python/7 install path/target/meson.build	
@@ -0,0 +1,3 @@
+testcase expect_error('Install-only backend cannot generate target rules, try using `--backend=ninja`.')
+    import('fs').copyfile('../test.py')
+endtestcase
diff --git a/tests/project/python/7 install path/test.json b/tests/project/python/7 install path/test.json
new file mode 100644
index 00000000..cf8e7a91
--- /dev/null
+++ b/tests/project/python/7 install path/test.json	
@@ -0,0 +1,18 @@
+{
+  "installed": [
+    {"type": "file", "file": "pure/one.py"},
+    {"type": "file", "file": "pure/two.py"},
+    {"type": "file", "file": "pure/alpha/one.py"},
+    {"type": "file", "file": "pure/alpha/two.py"},
+    {"type": "file", "file": "pure/alpha/three.py"},
+    {"type": "file", "file": "pure/beta/one.py"},
+    {"type": "file", "file": "plat/kw/test.py"},
+    {"type": "file", "file": "pure/kwrevert/test.py"},
+    {"type": "file", "file": "plat/test.py"},
+    {"type": "file", "file": "pure/test.py"},
+    {"type": "file", "file": "plat/data/test.py"},
+    {"type": "file", "file": "pure/data/test.py"},
+    {"type": "file", "file": "plat/kw/data/test.py"},
+    {"type": "file", "file": "pure/kwrevert/data/test.py"}
+  ]
+}
diff --git a/tests/project/python/7 install path/test.py b/tests/project/python/7 install path/test.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/project/python/8 different python versions/blaster.py b/tests/project/python/8 different python versions/blaster.py
new file mode 100755
index 00000000..163b6d42
--- /dev/null
+++ b/tests/project/python/8 different python versions/blaster.py	
@@ -0,0 +1,14 @@
+#!/usr/bin/env python
+
+import sys
+import tachyon
+
+result = tachyon.phaserize('shoot')
+
+if not isinstance(result, int):
+    print('Returned result not an integer.')
+    sys.exit(1)
+
+if result != 1:
+    print('Returned result {} is not 1.'.format(result))
+    sys.exit(1)
diff --git a/tests/project/python/8 different python versions/ext/meson.build b/tests/project/python/8 different python versions/ext/meson.build
new file mode 100644
index 00000000..b13bb326
--- /dev/null
+++ b/tests/project/python/8 different python versions/ext/meson.build	
@@ -0,0 +1,6 @@
+pylib = py.extension_module('tachyon',
+  'tachyon_module.c',
+  dependencies : py_dep,
+)
+
+pypathdir = meson.current_build_dir()
diff --git a/tests/project/python/8 different python versions/ext/tachyon_module.c b/tests/project/python/8 different python versions/ext/tachyon_module.c
new file mode 100644
index 00000000..68eda538
--- /dev/null
+++ b/tests/project/python/8 different python versions/ext/tachyon_module.c	
@@ -0,0 +1,59 @@
+/*
+  Copyright 2018 The Meson development team
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+*/
+
+/* A very simple Python extension module. */
+
+#include <Python.h>
+#include <string.h>
+
+static PyObject* phaserize(PyObject *self, PyObject *args) {
+    const char *message;
+    int result;
+
+    if(!PyArg_ParseTuple(args, "s", &message))
+        return NULL;
+
+    result = strcmp(message, "shoot") ? 0 : 1;
+#if PY_VERSION_HEX < 0x03000000
+    return PyInt_FromLong(result);
+#else
+    return PyLong_FromLong(result);
+#endif
+}
+
+static PyMethodDef TachyonMethods[] = {
+    {"phaserize",  phaserize, METH_VARARGS,
+     "Shoot tachyon cannons."},
+    {NULL, NULL, 0, NULL}
+};
+
+#if PY_VERSION_HEX < 0x03000000
+PyMODINIT_FUNC inittachyon(void) {
+    Py_InitModule("tachyon", TachyonMethods);
+}
+#else
+static struct PyModuleDef tachyonmodule = {
+   PyModuleDef_HEAD_INIT,
+   "tachyon",
+   NULL,
+   -1,
+   TachyonMethods
+};
+
+PyMODINIT_FUNC PyInit_tachyon(void) {
+    return PyModule_Create(&tachyonmodule);
+}
+#endif
diff --git a/tests/project/python/8 different python versions/meson.build b/tests/project/python/8 different python versions/meson.build
new file mode 100644
index 00000000..2655b066
--- /dev/null
+++ b/tests/project/python/8 different python versions/meson.build	
@@ -0,0 +1,34 @@
+project('Python extension module', 'c',
+  default_options : ['buildtype=release'])
+
+py_mod = import('python')
+
+py = py_mod.find_installation(get_option('python'), required : false)
+
+# CI images don't have 32-bit python2 for 32-bit windows,
+# so this actually gets detected then fails
+require = not (
+  get_option('python') == 'python2' and
+  host_machine.system() == 'windows' and
+  host_machine.cpu() == 'x86'
+)
+
+if py.found()
+  py_dep = py.dependency(required: require)
+
+  if py_dep.found()
+    subdir('ext')
+
+    test('extmod',
+      py,
+      args : files('blaster.py'),
+      env : ['PYTHONPATH=' + pypathdir])
+  else
+    error('MESON_SKIP_TEST: Python libraries not found, skipping test.')
+  endif
+else
+  error('MESON_SKIP_TEST: Python not found, skipping test.')
+endif
+
+py = py_mod.find_installation(get_option('python'), required : get_option('disabled_opt'))
+assert(not py.found(), 'find_installation not working with disabled feature')
diff --git a/tests/project/python/8 different python versions/meson_options.txt b/tests/project/python/8 different python versions/meson_options.txt
new file mode 100644
index 00000000..c85110d0
--- /dev/null
+++ b/tests/project/python/8 different python versions/meson_options.txt	
@@ -0,0 +1,4 @@
+option('python', type: 'string',
+  description: 'Name of or path to the python executable'
+)
+option('disabled_opt', type: 'feature', value: 'disabled')
diff --git a/tests/project/python/8 different python versions/test.json b/tests/project/python/8 different python versions/test.json
new file mode 100644
index 00000000..fe75a1c2
--- /dev/null
+++ b/tests/project/python/8 different python versions/test.json	
@@ -0,0 +1,13 @@
+{
+  "matrix": {
+    "options": {
+      "python": [
+        { "val": null },
+        { "val": "python2" },
+        { "val": "python3" },
+        { "val": "pypy" },
+        { "val": "pypy3" }
+      ]
+    }
+  }
+}
diff --git a/tests/project/python/9 extmodule limited api/limited.c b/tests/project/python/9 extmodule limited api/limited.c
new file mode 100644
index 00000000..0d1c7182
--- /dev/null
+++ b/tests/project/python/9 extmodule limited api/limited.c	
@@ -0,0 +1,19 @@
+#include <Python.h>
+
+#ifndef Py_LIMITED_API
+#error Py_LIMITED_API must be defined.
+#elif Py_LIMITED_API != 0x03070000
+#error Wrong value for Py_LIMITED_API
+#endif
+
+static struct PyModuleDef limited_module = {
+   PyModuleDef_HEAD_INIT,
+   "limited_api_test",
+   NULL,
+   -1,
+   NULL
+};
+
+PyMODINIT_FUNC PyInit_limited(void) {
+    return PyModule_Create(&limited_module);
+}
diff --git a/tests/project/python/9 extmodule limited api/meson.build b/tests/project/python/9 extmodule limited api/meson.build
new file mode 100644
index 00000000..68afc969
--- /dev/null
+++ b/tests/project/python/9 extmodule limited api/meson.build	
@@ -0,0 +1,16 @@
+project('Python limited api', 'c',
+  default_options : ['buildtype=release', 'werror=true'])
+
+py_mod = import('python')
+py = py_mod.find_installation()
+
+ext_mod_limited = py.extension_module('limited',
+  'limited.c',
+  limited_api: '3.7',
+  install: true,
+)
+
+ext_mod = py.extension_module('not_limited',
+  'not_limited.c',
+  install: true,
+)
diff --git a/tests/project/python/9 extmodule limited api/not_limited.c b/tests/project/python/9 extmodule limited api/not_limited.c
new file mode 100644
index 00000000..105dbb80
--- /dev/null
+++ b/tests/project/python/9 extmodule limited api/not_limited.c	
@@ -0,0 +1,59 @@
+#include <Python.h>
+#include <stdio.h>
+
+#ifdef Py_LIMITED_API
+#error Py_LIMITED_API must not be defined.
+#endif
+
+/* This function explicitly calls functions whose declaration is elided when
+ * Py_LIMITED_API is defined. This is to test that the linker is actually
+ * linking to the right version of the library on Windows. */
+static PyObject *meth_not_limited(PyObject *self, PyObject *args)
+{
+    PyObject *list;
+    Py_ssize_t size;
+
+    if (!PyArg_ParseTuple(args, "o", &  list))
+        return NULL;
+
+    if (!PyList_Check(list)) {
+        PyErr_Format(PyExc_TypeError, "expected 'list'");
+        return NULL;
+    }
+
+    /* PyList_GET_SIZE and PyList_GET_ITEM are only available if Py_LIMITED_API
+     * is not defined. It seems likely that they will remain excluded from the
+     * limited API as their checked counterparts (PyList_GetSize and
+     * PyList_GetItem) are made available in that mode instead. */
+    size = PyList_GET_SIZE(list);
+    for(Py_ssize_t i = 0; i < size; ++i) {
+        PyObject *element = PyList_GET_ITEM(list, i);
+        if (element == NULL) {
+            return NULL;
+        }
+
+        if(PyObject_Print(element, stdout, Py_PRINT_RAW) == -1) {
+            return NULL;
+        }
+    }
+
+    Py_RETURN_NONE;
+}
+
+static struct PyMethodDef not_limited_methods[] = {
+    { "not_limited", meth_not_limited, METH_VARARGS,
+     "Calls functions whose declaration is elided by Py_LIMITED_API" },
+    { NULL, NULL, 0, NULL }
+};
+
+static struct PyModuleDef not_limited_module = {
+   PyModuleDef_HEAD_INIT,
+   "not_limited_api_test",
+   NULL,
+   -1,
+   not_limited_methods
+};
+
+PyMODINIT_FUNC PyInit_not_limited(void) {
+    return PyModule_Create(&not_limited_module);
+}
diff --git a/tests/project/python/9 extmodule limited api/test.json b/tests/project/python/9 extmodule limited api/test.json
new file mode 100644
index 00000000..06a17062
--- /dev/null
+++ b/tests/project/python/9 extmodule limited api/test.json	
@@ -0,0 +1,8 @@
+{
+  "installed": [
+    {"type": "python_limited_lib", "file": "usr/@PYTHON_PLATLIB@/limited"},
+    {"type": "py_limited_implib", "file": "usr/@PYTHON_PLATLIB@/limited"},
+    {"type": "python_lib", "file": "usr/@PYTHON_PLATLIB@/not_limited"},
+    {"type": "py_implib", "file": "usr/@PYTHON_PLATLIB@/not_limited"}
+  ]
+}
-- 
2.45.1
Details
Message ID
<wcmksfmajga7734z2lvgmwmubwvrztrarpyj2a7ctial3zysxp@ut3h4sp4rnan>
In-Reply-To
<D1EOOT908W27.2G4D0B439AZGA@gmail.com> (view parent)
DKIM signature
pass
Download raw message
Hey, looks good, I've merged it!

Thanks,
Stone
Reply to thread Export thread (mbox)