diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d9a4b83..cbb60b3 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -12,9 +12,10 @@ on: jobs: build: - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} strategy: matrix: + os: [ubuntu-latest, windows-latest] python-version: [3.7, 3.8, 3.9] steps: diff --git a/lib/open_jtalk b/lib/open_jtalk index 2221314..9572293 160000 --- a/lib/open_jtalk +++ b/lib/open_jtalk @@ -1 +1 @@ -Subproject commit 22213146d3491d708ec9b827851de8c0bbca29d7 +Subproject commit 957229334996d2c9d9fcb73cdb3f4d9c15bcdd57 diff --git a/setup.py b/setup.py index ed627c8..e208534 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,11 @@ import os import subprocess +import sys +from distutils.errors import DistutilsExecError from distutils.version import LooseVersion +from distutils.spawn import spawn from glob import glob +from itertools import chain from os.path import exists, join from subprocess import run @@ -10,6 +14,8 @@ import setuptools.command.develop from setuptools import Extension, find_packages, setup +platform_is_windows = sys.platform == "win32" + version = "0.1.2" min_cython_ver = "0.21.0" @@ -21,6 +27,12 @@ except ImportError: _CYTHON_INSTALLED = False + +msvc_extra_compile_args_config = [ + "/source-charset:utf-8", + "/execution-charset:utf-8", +] + try: if not _CYTHON_INSTALLED: raise ImportError("No supported version of Cython installed.") @@ -32,13 +44,95 @@ if cython: ext = ".pyx" - cmdclass = {"build_ext": build_ext} + + def msvc_extra_compile_args(compile_args): + cas = set(compile_args) + xs = filter(lambda x: x not in cas, msvc_extra_compile_args_config) + return list(chain(compile_args, xs)) + + class custom_build_ext(build_ext): + def build_extensions(self): + compiler_type_is_msvc = self.compiler.compiler_type == "msvc" + for entry in self.extensions: + if compiler_type_is_msvc: + entry.extra_compile_args = msvc_extra_compile_args( + entry.extra_compile_args + if hasattr(entry, "extra_compile_args") + else [] + ) + + build_ext.build_extensions(self) + + cmdclass = {"build_ext": custom_build_ext} else: ext = ".cpp" cmdclass = {} if not os.path.exists(join("pyopenjtalk", "openjtalk" + ext)): raise RuntimeError("Cython is required to generate C++ code") + +# Workaround for `distutils.spawn` problem on Windows python < 3.9 +# See details: [bpo-39763: distutils.spawn now uses subprocess (GH-18743)] +# (https://github.com/python/cpython/commit/1ec63b62035e73111e204a0e03b83503e1c58f2e) +def test_quoted_arg_change(): + child_script = """ +import os +import sys +if len(sys.argv) > 5: + try: + os.makedirs(sys.argv[1], exist_ok=True) + with open(sys.argv[2], mode=sys.argv[3], encoding=sys.argv[4]) as fd: + fd.write(sys.argv[5]) + except OSError: + pass +""" + + try: + # write + package_build_dir = "build" + file_name = join(package_build_dir, "quoted_arg_output") + output_mode = "w" + file_encoding = "utf8" + arg_value = '"ARG"' + + spawn([ + sys.executable, + "-c", + child_script, + package_build_dir, + file_name, + output_mode, + file_encoding, + arg_value + ]) + + # read + with open(file_name, mode="r", encoding=file_encoding) as fd: + return fd.readline() != arg_value + except (DistutilsExecError, TypeError): + return False + + +def escape_string_macro_arg(s): + return s.replace('\\', '\\\\').replace('"', '\\"') + + +def escape_macro_element(x): + (k, arg) = x + return (k, escape_string_macro_arg(arg)) if type(arg) == str else x + + +def escape_macros(macros): + return list(map(escape_macro_element, macros)) + + +custom_define_macros = ( + escape_macros + if platform_is_windows and test_quoted_arg_change() + else (lambda macros: macros) +) + + # open_jtalk sources src_top = join("lib", "open_jtalk", "src") @@ -83,14 +177,14 @@ extra_compile_args=[], extra_link_args=[], language="c++", - define_macros=[ + define_macros=custom_define_macros([ ("HAVE_CONFIG_H", None), ("DIC_VERSION", 102), ("MECAB_DEFAULT_RC", '"dummy"'), ("PACKAGE", '"open_jtalk"'), ("VERSION", '"1.10"'), ("CHARSET_UTF_8", None), - ], + ]), ) ] @@ -104,6 +198,7 @@ include_dirs=[np.get_include(), join(htsengine_src_top, "include")], extra_compile_args=[], extra_link_args=[], + libraries=["winmm"] if platform_is_windows else [], language="c++", ) ] @@ -150,7 +245,7 @@ def run(self): cmdclass["develop"] = develop -with open("README.md", "r") as fd: +with open("README.md", "r", encoding="utf8") as fd: long_description = fd.read() setup(