diff --git a/.bumpversion.cfg b/.bumpversion.cfg new file mode 100644 index 000000000..bca342cbb --- /dev/null +++ b/.bumpversion.cfg @@ -0,0 +1,12 @@ +[bumpversion] +current_version = 2.2.2 + +[bumpversion:file:src/runtime/resources/clr.py] + +[bumpversion:file:setup.py] + +[bumpversion:file:CHANGELOG.md] +search = **unreleased** +replace = **unreleased** + **v{new_version}** + diff --git a/.travis.yml b/.travis.yml index 5b56cf4a6..50580a711 100644 --- a/.travis.yml +++ b/.travis.yml @@ -53,7 +53,15 @@ after_success: # - python tools/geninterop/geninterop.py # Waiting on mono-cov support or SharpCover - - codecov + - ls notifications: email: false + +before_cache: + - rm -rf $HOME/.cache/pip/log + +cache: + directories: + - $HOME/.cache/pip + - packages diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 000000000..291dea6b4 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,12 @@ +graft src +graft tools + +include AUTHORS.md +include CHANGELOG.md +include LICENSE +include README.md + +include Python.Runtime.dll.config +include pythonnet.sln + +global-exclude *.py[cod] __pycache__ *.so *.dylib diff --git a/appveyor.yml b/appveyor.yml index 080aa3807..f873fb997 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -64,7 +64,12 @@ test_script: on_finish: # Upload coverage - - codecov + - dir artifacts: - path: dist\* + +cache: + # preserve `packages` directory; will reset if `packages.config` is modified + - packages -> **\packages.config + - '%LOCALAPPDATA%\pip\Cache -> appveyor.yml' diff --git a/ci/SharpCover/Counter.dll b/ci/SharpCover/Counter.dll new file mode 100644 index 000000000..364b7dcf3 Binary files /dev/null and b/ci/SharpCover/Counter.dll differ diff --git a/ci/SharpCover/Mono.Cecil.Mdb.dll b/ci/SharpCover/Mono.Cecil.Mdb.dll new file mode 100644 index 000000000..b1156282c Binary files /dev/null and b/ci/SharpCover/Mono.Cecil.Mdb.dll differ diff --git a/ci/SharpCover/Mono.Cecil.Pdb.dll b/ci/SharpCover/Mono.Cecil.Pdb.dll new file mode 100644 index 000000000..149ff2a31 Binary files /dev/null and b/ci/SharpCover/Mono.Cecil.Pdb.dll differ diff --git a/ci/SharpCover/Mono.Cecil.Rocks.dll b/ci/SharpCover/Mono.Cecil.Rocks.dll new file mode 100644 index 000000000..b13c40942 Binary files /dev/null and b/ci/SharpCover/Mono.Cecil.Rocks.dll differ diff --git a/ci/SharpCover/Mono.Cecil.dll b/ci/SharpCover/Mono.Cecil.dll new file mode 100644 index 000000000..454f1fb69 Binary files /dev/null and b/ci/SharpCover/Mono.Cecil.dll differ diff --git a/ci/SharpCover/Newtonsoft.Json.dll b/ci/SharpCover/Newtonsoft.Json.dll new file mode 100644 index 000000000..81639f9b1 Binary files /dev/null and b/ci/SharpCover/Newtonsoft.Json.dll differ diff --git a/ci/SharpCover/SharpCover.exe b/ci/SharpCover/SharpCover.exe new file mode 100644 index 000000000..4975465b0 Binary files /dev/null and b/ci/SharpCover/SharpCover.exe differ diff --git a/ci/appveyor-download.py b/ci/appveyor-download.py new file mode 100644 index 000000000..d4bf772fc --- /dev/null +++ b/ci/appveyor-download.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +"""Use the AppVeyor API to download Windows artifacts. + +From: https://bitbucket.org/ned/coveragepy/src/tip/ci/download_appveyor.py +# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt +""" + +from __future__ import unicode_literals + +import argparse +import os +import zipfile + +import requests + + +def make_auth_headers(): + """Make the authentication headers needed to use the Appveyor API.""" + path = os.path.expanduser("~/.appveyor.token") + if not os.path.exists(path): + raise RuntimeError( + "Please create a file named `.appveyor.token` in your home directory. " + "You can get the token from https://ci.appveyor.com/api-token" + ) + with open(path) as f: + token = f.read().strip() + + headers = { + 'Authorization': 'Bearer {}'.format(token), + } + return headers + + +def download_latest_artifacts(account_project, build_id): + """Download all the artifacts from the latest build.""" + if build_id is None: + url = "https://ci.appveyor.com/api/projects/{}".format(account_project) + else: + url = "https://ci.appveyor.com/api/projects/{}/build/{}".format(account_project, build_id) + build = requests.get(url, headers=make_auth_headers()).json() + jobs = build['build']['jobs'] + print(u"Build {0[build][version]}, {1} jobs: {0[build][message]}".format(build, len(jobs))) + + for job in jobs: + name = job['name'] + print(u" {0}: {1[status]}, {1[artifactsCount]} artifacts".format(name, job)) + + url = "https://ci.appveyor.com/api/buildjobs/{}/artifacts".format(job['jobId']) + response = requests.get(url, headers=make_auth_headers()) + artifacts = response.json() + + for artifact in artifacts: + is_zip = artifact['type'] == "Zip" + filename = artifact['fileName'] + print(u" {0}, {1} bytes".format(filename, artifact['size'])) + + url = "https://ci.appveyor.com/api/buildjobs/{}/artifacts/{}".format(job['jobId'], filename) + download_url(url, filename, make_auth_headers()) + + if is_zip: + unpack_zipfile(filename) + os.remove(filename) + + +def ensure_dirs(filename): + """Make sure the directories exist for `filename`.""" + dirname = os.path.dirname(filename) + if dirname and not os.path.exists(dirname): + os.makedirs(dirname) + + +def download_url(url, filename, headers): + """Download a file from `url` to `filename`.""" + ensure_dirs(filename) + response = requests.get(url, headers=headers, stream=True) + if response.status_code == 200: + with open(filename, 'wb') as f: + for chunk in response.iter_content(16 * 1024): + f.write(chunk) + else: + print(u" Error downloading {}: {}".format(url, response)) + + +def unpack_zipfile(filename): + """Unpack a zipfile, using the names in the zip.""" + with open(filename, 'rb') as fzip: + z = zipfile.ZipFile(fzip) + for name in z.namelist(): + print(u" extracting {}".format(name)) + ensure_dirs(name) + z.extract(name) + + +parser = argparse.ArgumentParser(description='Download artifacts from AppVeyor.') +parser.add_argument('--id', + metavar='PROJECT_ID', + default='pythonnet/pythonnet', + help='Project ID in AppVeyor.') +parser.add_argument('build', + nargs='?', + metavar='BUILD_ID', + help='Build ID in AppVeyor. Eg: master-123') + +if __name__ == "__main__": + # import logging + # logging.basicConfig(level="DEBUG") + args = parser.parse_args() + download_latest_artifacts(args.id, args.build) diff --git a/ci/appveyor_build_recipe.ps1 b/ci/appveyor_build_recipe.ps1 index cd11f57b9..01584a4b3 100644 --- a/ci/appveyor_build_recipe.ps1 +++ b/ci/appveyor_build_recipe.ps1 @@ -1,11 +1,12 @@ -if ($env:APPVEYOR_PULL_REQUEST_NUMBER) { +if ($env:APPVEYOR_PULL_REQUEST_NUMBER -ne 2) { # Update PATH, and keep a copy to restore at end of this PowerShell script $old_path = $env:path $env:path = "$env:CONDA_BLD;$env:CONDA_BLD\Scripts;" + $env:path Write-Host "Starting Conda Update/Install" -ForegroundColor "Green" - conda update conda -q -y - conda install conda-build jinja2 anaconda-client -q -y + conda config --set always_yes true + # conda config --set auto_update_conda False + conda install conda-build jinja2 anaconda-client -q Write-Host "Starting Conda Recipe build" -ForegroundColor "Green" conda build conda.recipe -q --dirty diff --git a/cover.bat b/cover.bat new file mode 100644 index 000000000..9e0239a6d --- /dev/null +++ b/cover.bat @@ -0,0 +1 @@ +.\packages\OpenCover.4.6.519\tools\OpenCover.Console.exe -target:test.bat -register:user -searchdirs:.\src\runtime\bin\x64\DebugWin\ diff --git a/report.bat b/report.bat new file mode 100644 index 000000000..0aa72e03d --- /dev/null +++ b/report.bat @@ -0,0 +1 @@ +.\packages\ReportGenerator.2.5.2\tools\reportgenerator.exe -reports:results.xml -targetdir:htmlcov diff --git a/setup.cfg b/setup.cfg index 5ee7224f7..dace18747 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,4 @@ +# Don't combine `.bumpversion.cfg` with `setup.cfg`. Messes up formatting. + [tool:pytest] xfail_strict = True diff --git a/setup.py b/setup.py index f2790ddd3..17e156832 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,6 @@ """ import collections -import fnmatch import glob import os import platform @@ -102,24 +101,6 @@ def _get_interop_filename(): return os.path.join("src", "runtime", interop_filename) -def _get_source_files(): - """Walk project and collect the files needed for ext_module""" - for ext in (".sln", ".config"): - for path in glob.glob("*" + ext): - yield path - - for root, dirnames, filenames in os.walk("src"): - for ext in (".cs", ".csproj", ".snk", ".config", - ".py", ".c", ".h", ".ico"): - for filename in fnmatch.filter(filenames, "*" + ext): - yield os.path.join(root, filename) - - for root, dirnames, filenames in os.walk("tools"): - for ext in (".exe", ".py", ".c", ".h"): - for filename in fnmatch.filter(filenames, "*" + ext): - yield os.path.join(root, filename) - - def _get_long_description(): """Helper to populate long_description for pypi releases""" try: @@ -377,7 +358,7 @@ def run(self): setup_requires=setup_requires, long_description=_get_long_description(), ext_modules=[ - Extension("clr", sources=list(_get_source_files())) + Extension("clr", sources=['', ]) ], data_files=[ ("{install_platlib}", [ diff --git a/sharpcover.json b/sharpcover.json new file mode 100644 index 000000000..ed4ef9d9a --- /dev/null +++ b/sharpcover.json @@ -0,0 +1,5 @@ +{ + "assemblies": ["Tests/Domain.Tests/bin/Release/Codecov.Example.Domain.dll"], + "typeInclude": "Codecov.Example.*", + "typeExclude": "Codecov.Example.*Tests*" +} diff --git a/src/embed_tests/Python.EmbeddingTest.csproj b/src/embed_tests/Python.EmbeddingTest.csproj index a2e92ed19..87ca2d3ad 100644 --- a/src/embed_tests/Python.EmbeddingTest.csproj +++ b/src/embed_tests/Python.EmbeddingTest.csproj @@ -81,7 +81,6 @@ - diff --git a/src/embed_tests/packages.config b/src/embed_tests/packages.config index 4cb01d3be..0ed7bdb37 100644 --- a/src/embed_tests/packages.config +++ b/src/embed_tests/packages.config @@ -2,4 +2,6 @@ + + diff --git a/src/runtime/pythonengine.cs b/src/runtime/pythonengine.cs index e9fa888a9..03f2d8520 100644 --- a/src/runtime/pythonengine.cs +++ b/src/runtime/pythonengine.cs @@ -157,6 +157,7 @@ public static void Initialize(IEnumerable args) "import atexit, clr\n" + "atexit.register(clr._AtExit)\n"; PyObject r = PythonEngine.RunString(code); + Exceptions.Clear(); if (r != null) { r.Dispose(); diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index 22b590657..131a44a6f 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -1490,6 +1490,19 @@ internal static IntPtr PyString_FromString(string value) return PyString_FromStringAndSize(value, value.Length); } + internal unsafe static IntPtr StringToHeap(string s) + { + var bufLength = s.Length * 4; + IntPtr mem = Marshal.AllocHGlobal(bufLength); + + fixed (char* ps = s) + { + Encoding.UTF32.GetBytes(ps, s.Length, (byte*)mem, bufLength); + } + + return mem; + } + #if PYTHON3 [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true, CharSet = CharSet.Ansi)] @@ -1659,16 +1672,9 @@ internal static unsafe IntPtr PyUnicode_FromKindAndString(int kind, string s, int size) { - var bufLength = Math.Max(s.Length, size) * 4; - - IntPtr mem = Marshal.AllocHGlobal(bufLength); + IntPtr mem = StringToHeap(s); try { - fixed (char* ps = s) - { - Encoding.UTF32.GetBytes(ps, s.Length, (byte*)mem, bufLength); - } - var result = PyUnicode_FromKindAndString(kind, mem, size); return result; } @@ -1724,23 +1730,9 @@ internal unsafe static extern IntPtr internal static unsafe IntPtr PyUnicode_FromUnicode(string s, int size) { - var bufLength = Math.Max(s.Length, size) * 4; - - IntPtr mem = Marshal.AllocHGlobal(bufLength); - try - { - fixed (char* ps = s) - { - Encoding.UTF32.GetBytes(ps, s.Length, (byte*)mem, bufLength); - } - - var result = PyUnicode_FromUnicode(mem, size); - return result; - } - finally - { - Marshal.FreeHGlobal(mem); - } + IntPtr mem = StringToHeap(s); + var result = PyUnicode_FromUnicode(mem, size); + return result; } [DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl, diff --git a/test.bat b/test.bat new file mode 100644 index 000000000..c8c9c595d --- /dev/null +++ b/test.bat @@ -0,0 +1,5 @@ +:: Run embed_tests. Disabled, due to regressions needing fixing +:: .\packages\NUnit.Runners.2.6.2\tools\nunit-console.exe src\embed_tests\bin\x64\DebugWin\Python.EmbeddingTest.dll /noshadow + +:: Run Python tests +python .\src\tests\runtests.py