1#!/usr/bin/env python 2# -*- coding: utf-8 -*- 3 4"""test_setup 5---------------------------------- 6 7Tests for `skbuild.setup` function. 8""" 9 10import textwrap 11import sys 12import os 13import pprint 14import py.path 15import pytest 16 17from distutils.core import Distribution as distutils_Distribution 18from mock import patch 19from setuptools import Distribution as setuptool_Distribution 20 21from skbuild import setup as skbuild_setup 22from skbuild.constants import CMAKE_INSTALL_DIR, SKBUILD_DIR 23from skbuild.exceptions import SKBuildError 24from skbuild.platform_specifics import get_platform 25from skbuild.setuptools_wrap import strip_package 26from skbuild.utils import (push_dir, to_platform_path) 27 28from . import (_tmpdir, execute_setup_py, 29 initialize_git_repo_and_commit, is_site_reachable, push_argv) 30 31 32@pytest.mark.parametrize("distribution_type", 33 ['unknown', 34 'py_modules', 35 'packages', 36 'skbuild' 37 ]) 38def test_distribution_is_pure(distribution_type, tmpdir): 39 40 skbuild_setup_kwargs = {} 41 42 if distribution_type == 'unknown': 43 is_pure = False 44 45 elif distribution_type == 'py_modules': 46 is_pure = True 47 hello_py = tmpdir.join("hello.py") 48 hello_py.write("") 49 skbuild_setup_kwargs["py_modules"] = ["hello"] 50 51 elif distribution_type == 'packages': 52 is_pure = True 53 init_py = tmpdir.mkdir("hello").join("__init__.py") 54 init_py.write("") 55 skbuild_setup_kwargs["packages"] = ["hello"] 56 57 elif distribution_type == 'skbuild': 58 is_pure = False 59 cmakelists_txt = tmpdir.join("CMakeLists.txt") 60 cmakelists_txt.write( 61 """ 62 cmake_minimum_required(VERSION 3.5.0) 63 project(test NONE) 64 install(CODE "execute_process( 65 COMMAND \\${CMAKE_COMMAND} -E sleep 0)") 66 """ 67 ) 68 else: 69 raise Exception( 70 "Unknown distribution_type: {}".format(distribution_type)) 71 72 platform = get_platform() 73 original_write_test_cmakelist = platform.write_test_cmakelist 74 75 def write_test_cmakelist_no_languages(_self, _languages): 76 original_write_test_cmakelist([]) 77 78 with patch.object(type(platform), 'write_test_cmakelist', new=write_test_cmakelist_no_languages): 79 80 with push_dir(str(tmpdir)), push_argv(["setup.py", "build"]): 81 distribution = skbuild_setup( 82 name="test", 83 version="0.0.1", 84 description="test object returned by setup function", 85 author="The scikit-build team", 86 license="MIT", 87 **skbuild_setup_kwargs 88 ) 89 assert issubclass(distribution.__class__, 90 (distutils_Distribution, setuptool_Distribution)) 91 assert is_pure == distribution.is_pure() 92 93 94@pytest.mark.parametrize("cmake_args", [ 95 [], 96 ['--', '-DVAR:STRING=43', '-DVAR_WITH_SPACE:STRING=Ciao Mondo'] 97]) 98def test_cmake_args_keyword(cmake_args, capfd): 99 tmp_dir = _tmpdir('cmake_args_keyword') 100 101 tmp_dir.join('setup.py').write(textwrap.dedent( 102 """ 103 from skbuild import setup 104 setup( 105 name="test_cmake_args_keyword", 106 version="1.2.3", 107 description="a minimal example package", 108 author='The scikit-build team', 109 license="MIT", 110 cmake_args=[ 111 "-DVAR:STRING=42", 112 "-DVAR_WITH_SPACE:STRING=Hello World" 113 ] 114 115 ) 116 """ 117 )) 118 tmp_dir.join('CMakeLists.txt').write(textwrap.dedent( 119 """ 120 cmake_minimum_required(VERSION 3.5.0) 121 project(test NONE) 122 message(STATUS "VAR[${VAR}]") 123 message(STATUS "VAR_WITH_SPACE[${VAR_WITH_SPACE}]") 124 install(CODE "execute_process( 125 COMMAND \\${CMAKE_COMMAND} -E sleep 0)") 126 """ 127 )) 128 129 with execute_setup_py(tmp_dir, ['build'] + cmake_args, disable_languages_test=True): 130 pass 131 132 out, _ = capfd.readouterr() 133 134 if not cmake_args: 135 assert "VAR[42]" in out 136 assert "VAR_WITH_SPACE[Hello World]" in out 137 else: 138 assert "VAR[43]" in out 139 assert "VAR_WITH_SPACE[Ciao Mondo]" in out 140 141 142@pytest.mark.parametrize( 143 "cmake_install_dir, expected_failed, error_code_type", ( 144 (None, True, str), 145 ('', True, str), 146 (str(py.path.local.get_temproot().join('scikit-build')), True, SKBuildError), 147 ('banana', False, str) 148 ) 149) 150def test_cmake_install_dir_keyword( 151 cmake_install_dir, expected_failed, error_code_type, capsys): 152 153 # ------------------------------------------------------------------------- 154 # "SOURCE" tree layout: 155 # 156 # ROOT/ 157 # 158 # CMakeLists.txt 159 # setup.py 160 # 161 # apple/ 162 # __init__.py 163 # 164 # ------------------------------------------------------------------------- 165 # "BINARY" distribution layout 166 # 167 # ROOT/ 168 # 169 # apple/ 170 # __init__.py 171 # 172 173 tmp_dir = _tmpdir('cmake_install_dir_keyword') 174 175 setup_kwarg = '' 176 if cmake_install_dir is not None: 177 setup_kwarg = 'cmake_install_dir=r\'{}\''.format(cmake_install_dir) 178 179 tmp_dir.join('setup.py').write(textwrap.dedent( 180 """ 181 from skbuild import setup 182 setup( 183 name="test_cmake_install_dir", 184 version="1.2.3", 185 description="a package testing use of cmake_install_dir", 186 author='The scikit-build team', 187 license="MIT", 188 packages=['apple', 'banana'], 189 {setup_kwarg} 190 ) 191 """.format(setup_kwarg=setup_kwarg) 192 )) 193 194 # Install location purposely set to "." so that we can test 195 # usage of "cmake_install_dir" skbuild.setup keyword. 196 tmp_dir.join('CMakeLists.txt').write(textwrap.dedent( 197 """ 198 cmake_minimum_required(VERSION 3.5.0) 199 project(banana NONE) 200 file(WRITE "${CMAKE_BINARY_DIR}/__init__.py" "") 201 install(FILES "${CMAKE_BINARY_DIR}/__init__.py" DESTINATION ".") 202 """ 203 )) 204 205 tmp_dir.ensure('apple', '__init__.py') 206 207 failed = False 208 message = "" 209 try: 210 with execute_setup_py(tmp_dir, ['build'], disable_languages_test=True): 211 pass 212 except SystemExit as e: 213 # Error is not of type SKBuildError, it is expected to be 214 # raised by distutils.core.setup 215 failed = isinstance(e.code, error_code_type) 216 message = str(e) 217 218 out, _ = capsys.readouterr() 219 220 assert failed == expected_failed 221 if failed: 222 if error_code_type == str: 223 assert message == "error: package directory " \ 224 "'{}' does not exist".format( 225 os.path.join(CMAKE_INSTALL_DIR(), 'banana')) 226 else: 227 assert message.strip().startswith( 228 "setup parameter 'cmake_install_dir' " 229 "is set to an absolute path.") 230 else: 231 init_py = to_platform_path("{}/banana/__init__.py".format(CMAKE_INSTALL_DIR())) 232 assert "copying {}".format(init_py) in out 233 234 235@pytest.mark.parametrize("cmake_with_sdist", [True, False]) 236def test_cmake_with_sdist_keyword(cmake_with_sdist, capfd): 237 tmp_dir = _tmpdir('cmake_with_sdist') 238 239 tmp_dir.join('setup.py').write(textwrap.dedent( 240 """ 241 from skbuild import setup 242 setup( 243 name="cmake_with_sdist_keyword", 244 version="1.2.3", 245 description="a minimal example package", 246 author='The scikit-build team', 247 license="MIT", 248 cmake_with_sdist={cmake_with_sdist} 249 ) 250 """.format(cmake_with_sdist=cmake_with_sdist) 251 )) 252 tmp_dir.join('CMakeLists.txt').write(textwrap.dedent( 253 """ 254 cmake_minimum_required(VERSION 3.5.0) 255 project(test NONE) 256 install(CODE "execute_process( 257 COMMAND \\${CMAKE_COMMAND} -E sleep 0)") 258 """ 259 )) 260 261 initialize_git_repo_and_commit(tmp_dir) 262 263 with execute_setup_py(tmp_dir, ['sdist'], disable_languages_test=True): 264 pass 265 266 out, _ = capfd.readouterr() 267 268 if cmake_with_sdist: 269 assert "Generating done" in out 270 else: 271 assert "Generating done" not in out 272 273 274def test_cmake_minimum_required_version_keyword(): 275 tmp_dir = _tmpdir('cmake_minimum_required_version') 276 277 tmp_dir.join('setup.py').write(textwrap.dedent( 278 """ 279 from skbuild import setup 280 setup( 281 name="cmake_with_sdist_keyword", 282 version="1.2.3", 283 description="a minimal example package", 284 author='The scikit-build team', 285 license="MIT", 286 cmake_minimum_required_version='99.98.97' 287 ) 288 """ 289 )) 290 tmp_dir.join('CMakeLists.txt').write(textwrap.dedent( 291 """ 292 cmake_minimum_required(VERSION 3.5.0) 293 project(test NONE) 294 install(CODE "execute_process( 295 COMMAND \\${CMAKE_COMMAND} -E sleep 0)") 296 """ 297 )) 298 299 try: 300 with execute_setup_py(tmp_dir, ['build'], disable_languages_test=True): 301 pass 302 except SystemExit as e: 303 # Error is not of type SKBuildError, it is expected to be 304 # raised by distutils.core.setup 305 failed = isinstance(e.code, SKBuildError) 306 message = str(e) 307 assert failed 308 assert "CMake version 99.98.97 or higher is required." in message 309 310 311@pytest.mark.skipif(os.environ.get("CONDA_BUILD", "0") == "1", 312 reason="running tests expecting network connection in Conda is not possible. " 313 "See https://github.com/conda/conda/issues/508") 314@pytest.mark.skipif(not is_site_reachable("https://pypi.org/simple/cmake/"), 315 reason="pypi.org website not reachable") 316@pytest.mark.xfail(sys.platform.startswith("win"), 317 reason="Timing issue with Ninja & MSVC 2019- needs investigation", 318 strict=False) 319def test_setup_requires_keyword_include_cmake(mocker, capsys): 320 321 mock_setup = mocker.patch('skbuild.setuptools_wrap.upstream_setup') 322 323 tmp_dir = _tmpdir('setup_requires_keyword_include_cmake') 324 325 setup_requires = ['cmake>=3.10'] 326 327 tmp_dir.join('setup.py').write(textwrap.dedent( 328 """ 329 from skbuild import setup 330 setup( 331 name="cmake_with_sdist_keyword", 332 version="1.2.3", 333 description="a minimal example package", 334 author='The scikit-build team', 335 license="MIT", 336 setup_requires=[{setup_requires}] 337 ) 338 """.format(setup_requires=",".join(["'%s'" % package for package in setup_requires])) 339 )) 340 tmp_dir.join('CMakeLists.txt').write(textwrap.dedent( 341 """ 342 cmake_minimum_required(VERSION 3.5.0) 343 project(test NONE) 344 install(CODE "execute_process( 345 COMMAND \\${CMAKE_COMMAND} -E sleep 0)") 346 """ 347 )) 348 349 with execute_setup_py(tmp_dir, ['build'], disable_languages_test=True): 350 assert mock_setup.call_count == 1 351 setup_kw = mock_setup.call_args[1] 352 assert setup_kw['setup_requires'] == setup_requires 353 354 import cmake 355 356 out, _ = capsys.readouterr() 357 if "Searching for cmake>=3.10" in out: 358 assert cmake.__file__.lower().startswith(str(tmp_dir).lower()) 359 360 361@pytest.mark.parametrize("distribution_type", ('pure', 'skbuild')) 362def test_script_keyword(distribution_type, capsys): 363 364 # ------------------------------------------------------------------------- 365 # 366 # "SOURCE" tree layout for "pure" distribution: 367 # 368 # ROOT/ 369 # setup.py 370 # foo.py 371 # bar.py 372 # 373 # "SOURCE" tree layout for "pure" distribution: 374 # 375 # ROOT/ 376 # setup.py 377 # CMakeLists.txt 378 # 379 # ------------------------------------------------------------------------- 380 # "BINARY" distribution layout is identical for both 381 # 382 # ROOT/ 383 # foo.py 384 # bar.py 385 # 386 387 tmp_dir = _tmpdir('script_keyword') 388 389 tmp_dir.join('setup.py').write(textwrap.dedent( 390 """ 391 from skbuild import setup 392 setup( 393 name="test_script_keyword", 394 version="1.2.3", 395 description="a package testing use of script keyword", 396 author='The scikit-build team', 397 license="MIT", 398 scripts=['foo.py', 'bar.py'] 399 ) 400 """ 401 )) 402 403 if distribution_type == 'skbuild': 404 tmp_dir.join('CMakeLists.txt').write(textwrap.dedent( 405 """ 406 cmake_minimum_required(VERSION 3.5.0) 407 project(foo NONE) 408 file(WRITE "${CMAKE_BINARY_DIR}/foo.py" "# foo.py") 409 file(WRITE "${CMAKE_BINARY_DIR}/bar.py" "# bar.py") 410 install( 411 FILES 412 "${CMAKE_BINARY_DIR}/foo.py" 413 "${CMAKE_BINARY_DIR}/bar.py" 414 DESTINATION "." 415 ) 416 """ 417 )) 418 419 messages = [ 420 "copying {}/{}.py -> " 421 "{}/setuptools/scripts-".format(CMAKE_INSTALL_DIR(), module, SKBUILD_DIR()) 422 for module in ['foo', 'bar']] 423 424 elif distribution_type == 'pure': 425 tmp_dir.join('foo.py').write("# foo.py") 426 tmp_dir.join('bar.py').write("# bar.py") 427 428 messages = [ 429 "copying {}.py -> " 430 "{}/setuptools/scripts-".format(module, SKBUILD_DIR()) 431 for module in ['foo', 'bar']] 432 433 with execute_setup_py(tmp_dir, ['build'], disable_languages_test=True): 434 pass 435 436 out, _ = capsys.readouterr() 437 for message in messages: 438 assert to_platform_path(message) in out 439 440 441@pytest.mark.parametrize("distribution_type", ('pure', 'skbuild')) 442def test_py_modules_keyword(distribution_type, capsys): 443 444 # ------------------------------------------------------------------------- 445 # 446 # "SOURCE" tree layout for "pure" distribution: 447 # 448 # ROOT/ 449 # setup.py 450 # foo.py 451 # bar.py 452 # 453 # "SOURCE" tree layout for "skbuild" distribution: 454 # 455 # ROOT/ 456 # setup.py 457 # CMakeLists.txt 458 # 459 # ------------------------------------------------------------------------- 460 # "BINARY" distribution layout is identical for both 461 # 462 # ROOT/ 463 # foo.py 464 # bar.py 465 # 466 467 tmp_dir = _tmpdir('py_modules_keyword') 468 469 tmp_dir.join('setup.py').write(textwrap.dedent( 470 """ 471 from skbuild import setup 472 setup( 473 name="test_py_modules_keyword", 474 version="1.2.3", 475 description="a package testing use of py_modules keyword", 476 author='The scikit-build team', 477 license="MIT", 478 py_modules=['foo', 'bar'] 479 ) 480 """ 481 )) 482 483 if distribution_type == 'skbuild': 484 tmp_dir.join('CMakeLists.txt').write(textwrap.dedent( 485 """ 486 cmake_minimum_required(VERSION 3.5.0) 487 project(foobar NONE) 488 file(WRITE "${CMAKE_BINARY_DIR}/foo.py" "# foo.py") 489 file(WRITE "${CMAKE_BINARY_DIR}/bar.py" "# bar.py") 490 install( 491 FILES 492 "${CMAKE_BINARY_DIR}/foo.py" 493 "${CMAKE_BINARY_DIR}/bar.py" 494 DESTINATION "." 495 ) 496 """ 497 )) 498 499 messages = [ 500 "copying {}/{}.py -> " 501 "{}/setuptools/lib".format(CMAKE_INSTALL_DIR(), module, SKBUILD_DIR()) 502 for module in ['foo', 'bar']] 503 504 elif distribution_type == 'pure': 505 tmp_dir.join('foo.py').write("# foo.py") 506 tmp_dir.join('bar.py').write("# bar.py") 507 508 messages = [ 509 "copying {}.py -> " 510 "{}/setuptools/lib".format(module, SKBUILD_DIR()) 511 for module in ['foo', 'bar']] 512 513 with execute_setup_py(tmp_dir, ['build'], disable_languages_test=True): 514 pass 515 516 out, _ = capsys.readouterr() 517 for message in messages: 518 assert to_platform_path(message) in out 519 520 521@pytest.mark.parametrize("package_parts, module_file, expected", [ 522 ([], "", ""), 523 ([""], "file.py", "file.py"), 524 ([], "foo/file.py", "foo/file.py"), 525 (["foo"], "", ""), 526 (["foo"], "", ""), 527 (["foo"], "foo/file.py", "file.py"), 528 (["foo"], "foo\\file.py", "file.py"), 529 (["foo", "bar"], "foo/file.py", "foo/file.py"), 530 (["foo", "bar"], "foo/bar/file.py", "file.py"), 531 (["foo", "bar"], "foo/bar/baz/file.py", "baz/file.py"), 532 (["foo"], "/foo/file.py", "/foo/file.py"), 533]) 534def test_strip_package(package_parts, module_file, expected): 535 assert strip_package(package_parts, module_file) == expected 536 537 538@pytest.mark.parametrize("has_cmake_package", [0, 1]) # noqa: C901 539@pytest.mark.parametrize("has_cmake_module", [0, 1]) 540@pytest.mark.parametrize("has_hybrid_package", [0, 1]) 541@pytest.mark.parametrize("has_pure_package", [0, 1]) 542@pytest.mark.parametrize("has_pure_module", [0, 1]) 543@pytest.mark.parametrize("with_package_base", [0, 1]) 544def test_setup_inputs( 545 has_cmake_package, has_cmake_module, 546 has_hybrid_package, 547 has_pure_package, has_pure_module, 548 with_package_base, 549 mocker): 550 """This test that a project can have a package with some modules 551 installed using setup.py and some other modules installed using CMake. 552 """ 553 554 tmp_dir = _tmpdir('test_setup_inputs') 555 556 package_base = 'to/the/base' if with_package_base else '' 557 package_base_dir = package_base + '/' if package_base else '' 558 cmake_source_dir = package_base 559 560 if cmake_source_dir and (has_cmake_package or has_cmake_module): 561 pytest.skip("unsupported configuration: " 562 "python package fully generated by CMake does *NOT* work. " 563 "At least __init__.py should be in the project source tree") 564 565 # ------------------------------------------------------------------------- 566 # Here is the "SOURCE" tree layout: 567 # 568 # ROOT/ 569 # 570 # setup.py 571 # 572 # [<base>/] 573 # 574 # pureModule.py 575 # 576 # pure/ 577 # __init__.py 578 # pure.py 579 # 580 # data/ 581 # pure.dat 582 # 583 # [<cmake_src_dir>/] 584 # 585 # hybrid/ 586 # CMakeLists.txt 587 # __init__.py 588 # hybrid_pure.dat 589 # hybrid_pure.py 590 # 591 # data/ 592 # hybrid_data_pure.dat 593 # 594 # hybrid_2/ 595 # __init__.py 596 # hybrid_2_pure.py 597 # 598 # hybrid_2_pure/ 599 # __init__.py 600 # hybrid_2_pure_1.py 601 # hybrid_2_pure_2.py 602 # 603 # 604 # ------------------------------------------------------------------------- 605 # and here is the "BINARY" distribution layout: 606 # 607 # The comment "CMake" or "Setuptools" indicates which tool is responsible 608 # for placing the file in the tree used to create the binary distribution. 609 # 610 # ROOT/ 611 # 612 # cmakeModule.py # CMake 613 # 614 # cmake/ 615 # __init__.py # CMake 616 # cmake.py # CMake 617 # 618 # hybrid/ 619 # hybrid_cmake.dat # CMake 620 # hybrid_cmake.py # CMake 621 # hybrid_pure.dat # Setuptools 622 # hybrid_pure.py # Setuptools 623 # 624 # data/ 625 # hybrid_data_pure.dat # CMake or Setuptools 626 # hybrid_data_cmake.dat # CMake *NO TEST* 627 # 628 # hybrid_2/ 629 # __init__.py # CMake or Setuptools 630 # hybrid_2_pure.py # CMake or Setuptools 631 # hybrid_2_cmake.py # CMake 632 # 633 # hybrid_2_pure/ 634 # __init__.py # CMake or Setuptools 635 # hybrid_2_pure_1.py # CMake or Setuptools 636 # hybrid_2_pure_2.py # CMake or Setuptools 637 # 638 # pureModule.py # Setuptools 639 # 640 # pure/ 641 # __init__.py # Setuptools 642 # pure.py # Setuptools 643 # 644 # data/ 645 # pure.dat # Setuptools 646 647 tmp_dir.join('setup.py').write(textwrap.dedent( 648 """ 649 from skbuild import setup 650 #from setuptools import setup 651 setup( 652 name="test_hybrid_project", 653 version="1.2.3", 654 description=("an hybrid package mixing files installed by both " 655 "CMake and setuptools"), 656 author='The scikit-build team', 657 license="MIT", 658 cmake_source_dir='{cmake_source_dir}', 659 cmake_install_dir='{cmake_install_dir}', 660 # Arbitrary order of packages 661 packages=[ 662 {p_off} 'pure', 663 {h_off} 'hybrid.hybrid_2', 664 {h_off} 'hybrid', 665 {c_off} 'cmake', 666 {p_off} 'hybrid.hybrid_2_pure', 667 ], 668 py_modules=[ 669 {pm_off} '{package_base}pureModule', 670 {cm_off} '{package_base}cmakeModule', 671 ], 672 package_data={{ 673 {p_off} 'pure': ['data/pure.dat'], 674 {h_off} 'hybrid': ['hybrid_pure.dat', 'data/hybrid_data_pure.dat'], 675 }}, 676 # Arbitrary order of package_dir 677 package_dir = {{ 678 {p_off} 'hybrid.hybrid_2_pure': '{package_base}hybrid/hybrid_2_pure', 679 {p_off} 'pure': '{package_base}pure', 680 {h_off} 'hybrid': '{package_base}hybrid', 681 {h_off} 'hybrid.hybrid_2': '{package_base}hybrid/hybrid_2', 682 {c_off} 'cmake': '{package_base}cmake', 683 }} 684 ) 685 """.format( 686 cmake_source_dir=cmake_source_dir, 687 cmake_install_dir=package_base, 688 package_base=package_base_dir, 689 c_off='' if has_cmake_package else '#', 690 cm_off='' if has_cmake_module else '#', 691 h_off='' if has_hybrid_package else '#', 692 p_off='' if has_pure_package else '#', 693 pm_off='' if has_pure_module else '#' 694 ) 695 )) 696 697 src_dir = tmp_dir.ensure(package_base, dir=1) 698 699 src_dir.join('CMakeLists.txt').write(textwrap.dedent( 700 """ 701 cmake_minimum_required(VERSION 3.5.0) 702 project(hybrid NONE) 703 set(build_dir ${{CMAKE_BINARY_DIR}}) 704 705 {c_off} file(WRITE ${{build_dir}}/__init__.py "") 706 {c_off} file(WRITE ${{build_dir}}/cmake.py "") 707 {c_off} install( 708 {c_off} FILES 709 {c_off} ${{build_dir}}/__init__.py 710 {c_off} ${{build_dir}}/cmake.py 711 {c_off} DESTINATION cmake 712 {c_off} ) 713 714 {cm_off} file(WRITE ${{build_dir}}/cmakeModule.py "") 715 {cm_off} install( 716 {cm_off} FILES ${{build_dir}}/cmakeModule.py 717 {cm_off} DESTINATION .) 718 719 {h_off} file(WRITE ${{build_dir}}/hybrid_cmake.dat "") 720 {h_off} install( 721 {h_off} FILES ${{build_dir}}/hybrid_cmake.dat 722 {h_off} DESTINATION hybrid) 723 724 {h_off} file(WRITE ${{build_dir}}/hybrid_cmake.py "") 725 {h_off} install( 726 {h_off} FILES ${{build_dir}}/hybrid_cmake.py 727 {h_off} DESTINATION hybrid) 728 729 {h_off} file(WRITE ${{build_dir}}/hybrid_data_cmake.dat "") 730 {h_off} install( 731 {h_off} FILES ${{build_dir}}/hybrid_data_cmake.dat 732 {h_off} DESTINATION hybrid/data) 733 734 {h_off} file(WRITE ${{build_dir}}/hybrid_2_cmake.py "") 735 {h_off} install( 736 {h_off} FILES ${{build_dir}}/hybrid_2_cmake.py 737 {h_off} DESTINATION hybrid/hybrid_2) 738 739 install(CODE "message(STATUS \\\"Installation complete\\\")") 740 """.format( 741 c_off='' if has_cmake_package else '#', 742 cm_off='' if has_cmake_module else '#', 743 h_off='' if has_hybrid_package else '#', 744 ) 745 )) 746 747 # List path types: 'c', 'cm', 'h', 'p' or 'pm' 748 try: 749 path_types = list(zip(*filter( 750 lambda i: i[1], 751 [('c', has_cmake_package), 752 ('cm', has_cmake_module), 753 ('h', has_hybrid_package), 754 ('p', has_pure_package), 755 ('pm', has_pure_module)])))[0] 756 except IndexError: 757 path_types = [] 758 759 def select_paths(annotated_paths): 760 """Return a filtered list paths considering ``path_types``. 761 762 `annotated_paths`` is list of tuple ``(type, path)`` where type 763 is either `c`, 'cm', `h`, `p` or 'pm'. 764 765 """ 766 return filter(lambda i: i[0] in path_types, annotated_paths) 767 768 # Commented paths are the one expected to be installed by CMake. For 769 # this reason, corresponding files should NOT be created in the source 770 # tree. 771 for (_type, path) in select_paths([ 772 # ('c', 'cmake/__init__.py'), 773 # ('c', 'cmake/cmake.py'), 774 775 # ('cm', 'cmakeModule.py'), 776 777 ('h', 'hybrid/__init__.py'), 778 # ('h', 'hybrid/hybrid_cmake.dat'), 779 # ('h', 'hybrid/hybrid_cmake.py'), 780 ('h', 'hybrid/hybrid_pure.dat'), 781 ('h', 'hybrid/hybrid_pure.py'), 782 783 # ('h', 'hybrid/data/hybrid_data_cmake.dat'), 784 ('h', 'hybrid/data/hybrid_data_pure.dat'), 785 786 ('h', 'hybrid/hybrid_2/__init__.py'), 787 # ('h', 'hybrid/hybrid_2/hybrid_2_cmake.py'), 788 ('h', 'hybrid/hybrid_2/hybrid_2_pure.py'), 789 790 ('p', 'hybrid/hybrid_2_pure/__init__.py'), 791 ('p', 'hybrid/hybrid_2_pure/hybrid_2_pure_1.py'), 792 ('p', 'hybrid/hybrid_2_pure/hybrid_2_pure_2.py'), 793 794 ('pm', 'pureModule.py'), 795 796 ('p', 'pure/__init__.py'), 797 ('p', 'pure/pure.py'), 798 799 ('p', 'pure/data/pure.dat'), 800 ]): 801 assert _type in ['p', 'pm', 'h'] 802 root = (package_base 803 if (_type == 'p' or _type == 'pm') 804 else cmake_source_dir) 805 tmp_dir.ensure(os.path.join(root, path)) 806 807 # Do not call the real setup function. Instead, replace it with 808 # a MagicMock allowing to check with which arguments it was invoked. 809 mock_setup = mocker.patch('skbuild.setuptools_wrap.upstream_setup') 810 811 # Convenience print function 812 def _pprint(desc, value=None): 813 print( 814 "-----------------\n" 815 "{}:\n" 816 "\n" 817 "{}\n".format(desc, pprint.pformat( 818 setup_kw.get(desc, {}) if value is None else value, indent=2))) 819 820 with execute_setup_py(tmp_dir, ['build'], disable_languages_test=True): 821 822 assert mock_setup.call_count == 1 823 setup_kw = mock_setup.call_args[1] 824 825 # packages 826 expected_packages = [] 827 if has_cmake_package: 828 expected_packages += ['cmake'] 829 if has_hybrid_package: 830 expected_packages += ['hybrid', 'hybrid.hybrid_2'] 831 if has_pure_package: 832 expected_packages += ['hybrid.hybrid_2_pure', 'pure'] 833 834 _pprint('expected_packages', expected_packages) 835 _pprint('packages') 836 837 # package dir 838 expected_package_dir = { 839 package: (os.path.join(CMAKE_INSTALL_DIR(), 840 package_base, 841 package.replace('.', '/'))) 842 for package in expected_packages 843 } 844 _pprint('expected_package_dir', expected_package_dir) 845 _pprint('package_dir') 846 847 # package data 848 expected_package_data = {} 849 850 if has_cmake_package: 851 expected_package_data['cmake'] = [ 852 '__init__.py', 853 'cmake.py' 854 ] 855 856 if has_hybrid_package: 857 expected_package_data['hybrid'] = [ 858 '__init__.py', 859 'hybrid_cmake.dat', 860 'hybrid_cmake.py', 861 'hybrid_pure.dat', 862 'hybrid_pure.py', 863 'data/hybrid_data_cmake.dat', 864 'data/hybrid_data_pure.dat', 865 ] 866 expected_package_data['hybrid.hybrid_2'] = [ 867 '__init__.py', 868 'hybrid_2_cmake.py', 869 'hybrid_2_pure.py' 870 ] 871 872 if has_pure_package: 873 expected_package_data['hybrid.hybrid_2_pure'] = \ 874 [ 875 '__init__.py', 876 'hybrid_2_pure_1.py', 877 'hybrid_2_pure_2.py' 878 ] 879 expected_package_data['pure'] = [ 880 '__init__.py', 881 'pure.py', 882 'data/pure.dat', 883 ] 884 885 if has_cmake_module or has_pure_module: 886 expected_modules = [] 887 if has_cmake_module: 888 expected_modules.append(package_base_dir + 'cmakeModule.py') 889 if has_pure_module: 890 expected_modules.append(package_base_dir + 'pureModule.py') 891 expected_package_data[''] = expected_modules 892 893 _pprint('expected_package_data', expected_package_data) 894 package_data = {p: sorted(files) 895 for p, files in setup_kw['package_data'].items()} 896 897 _pprint('package_data', package_data) 898 899 # py_modules (corresponds to files associated with empty package) 900 expected_py_modules = [] 901 if '' in expected_package_data: 902 expected_py_modules = [ 903 os.path.splitext(module_file)[0] 904 for module_file in expected_package_data['']] 905 _pprint('expected_py_modules', expected_py_modules) 906 _pprint('py_modules') 907 908 # scripts 909 expected_scripts = [] 910 _pprint('expected_scripts', expected_scripts) 911 _pprint('scripts') 912 913 # data_files 914 expected_data_files = [] 915 _pprint('expected_data_files', expected_data_files) 916 _pprint('data_files') 917 918 assert sorted(setup_kw['packages']) == sorted(expected_packages) 919 assert sorted(setup_kw['package_dir']) == sorted(expected_package_dir) 920 assert package_data == {p: sorted(files) 921 for p, files in expected_package_data.items()} 922 assert sorted(setup_kw['py_modules']) == sorted(expected_py_modules) 923 assert sorted(setup_kw['scripts']) == sorted([]) 924 assert sorted(setup_kw['data_files']) == sorted([]) 925 926 927@pytest.mark.parametrize("with_cmake_source_dir", [0, 1]) 928def test_cmake_install_into_pure_package(with_cmake_source_dir, capsys): 929 930 # ------------------------------------------------------------------------- 931 # "SOURCE" tree layout: 932 # 933 # (1) with_cmake_source_dir == 0 934 # 935 # ROOT/ 936 # 937 # CMakeLists.txt 938 # setup.py 939 # 940 # fruits/ 941 # __init__.py 942 # 943 # 944 # (2) with_cmake_source_dir == 1 945 # 946 # ROOT/ 947 # 948 # setup.py 949 # 950 # fruits/ 951 # __init__.py 952 # 953 # src/ 954 # 955 # CMakeLists.txt 956 # 957 # ------------------------------------------------------------------------- 958 # "BINARY" distribution layout: 959 # 960 # ROOT/ 961 # 962 # fruits/ 963 # 964 # __init__.py 965 # apple.py 966 # banana.py 967 # 968 # data/ 969 # 970 # apple.dat 971 # banana.dat 972 # 973 974 tmp_dir = _tmpdir('cmake_install_into_pure_package') 975 976 cmake_source_dir = 'src' if with_cmake_source_dir else '' 977 978 tmp_dir.join('setup.py').write(textwrap.dedent( 979 """ 980 from skbuild import setup 981 setup( 982 name="test_py_modules_keyword", 983 version="1.2.3", 984 description="a package testing use of py_modules keyword", 985 author='The scikit-build team', 986 license="MIT", 987 packages=['fruits'], 988 cmake_install_dir='fruits', 989 cmake_source_dir='{cmake_source_dir}', 990 ) 991 """.format(cmake_source_dir=cmake_source_dir) 992 )) 993 994 cmake_src_dir = tmp_dir.ensure(cmake_source_dir, dir=1) 995 cmake_src_dir.join('CMakeLists.txt').write(textwrap.dedent( 996 """ 997 cmake_minimum_required(VERSION 3.5.0) 998 project(test NONE) 999 file(WRITE "${CMAKE_BINARY_DIR}/apple.py" "# apple.py") 1000 file(WRITE "${CMAKE_BINARY_DIR}/banana.py" "# banana.py") 1001 install( 1002 FILES 1003 "${CMAKE_BINARY_DIR}/apple.py" 1004 "${CMAKE_BINARY_DIR}/banana.py" 1005 DESTINATION "." 1006 ) 1007 file(WRITE "${CMAKE_BINARY_DIR}/apple.dat" "# apple.dat") 1008 file(WRITE "${CMAKE_BINARY_DIR}/banana.dat" "# banana.dat") 1009 install( 1010 FILES 1011 "${CMAKE_BINARY_DIR}/apple.dat" 1012 "${CMAKE_BINARY_DIR}/banana.dat" 1013 DESTINATION "data" 1014 ) 1015 """ 1016 )) 1017 1018 tmp_dir.ensure('fruits/__init__.py') 1019 1020 with execute_setup_py(tmp_dir, ['build'], disable_languages_test=True): 1021 pass 1022 1023 messages = [ 1024 "copying {}/{} -> " 1025 "{}/setuptools/lib".format(CMAKE_INSTALL_DIR(), module, SKBUILD_DIR()) 1026 for module in [ 1027 'fruits/__init__.py', 1028 'fruits/apple.py', 1029 'fruits/banana.py', 1030 'fruits/data/apple.dat', 1031 'fruits/data/banana.dat', 1032 ]] 1033 1034 out, _ = capsys.readouterr() 1035 for message in messages: 1036 assert to_platform_path(message) in out 1037 1038 1039@pytest.mark.parametrize("zip_safe", [None, False, True]) 1040def test_zip_safe_default(zip_safe, mocker): 1041 1042 mock_setup = mocker.patch('skbuild.setuptools_wrap.upstream_setup') 1043 1044 tmp_dir = _tmpdir('zip_safe_default') 1045 1046 setup_kwarg = '' 1047 if zip_safe is not None: 1048 setup_kwarg = 'zip_safe={}'.format(zip_safe) 1049 1050 tmp_dir.join('setup.py').write(textwrap.dedent( 1051 """ 1052 from skbuild import setup 1053 setup( 1054 name="zip_safe_default", 1055 version="1.2.3", 1056 description="a minimal example package", 1057 author='The scikit-build team', 1058 license="MIT", 1059 {setup_kwarg} 1060 ) 1061 """.format(setup_kwarg=setup_kwarg) 1062 )) 1063 tmp_dir.join('CMakeLists.txt').write(textwrap.dedent( 1064 """ 1065 cmake_minimum_required(VERSION 3.5.0) 1066 project(test NONE) 1067 install(CODE "execute_process( 1068 COMMAND \\${CMAKE_COMMAND} -E sleep 0)") 1069 """ 1070 )) 1071 1072 with execute_setup_py(tmp_dir, ['build'], disable_languages_test=True): 1073 pass 1074 1075 assert mock_setup.call_count == 1 1076 setup_kw = mock_setup.call_args[1] 1077 1078 assert "zip_safe" in setup_kw 1079 if zip_safe is None: 1080 assert not setup_kw["zip_safe"] 1081 elif zip_safe: 1082 assert setup_kw["zip_safe"] 1083 else: # zip_safe is False 1084 assert not setup_kw["zip_safe"] 1085