diff --git a/flit/wheel.py b/flit/wheel.py index a53c3f6e..a60e3586 100644 --- a/flit/wheel.py +++ b/flit/wheel.py @@ -4,8 +4,8 @@ log = logging.getLogger(__name__) -def make_wheel_in(ini_path, wheel_directory): - return core_wheel.make_wheel_in(ini_path, wheel_directory) +def make_wheel_in(ini_path, wheel_directory, editable=False): + return core_wheel.make_wheel_in(ini_path, wheel_directory, editable) class WheelBuilder(core_wheel.WheelBuilder): pass diff --git a/flit_core/flit_core/buildapi.py b/flit_core/flit_core/buildapi.py index b7cb21de..963bf614 100644 --- a/flit_core/flit_core/buildapi.py +++ b/flit_core/flit_core/buildapi.py @@ -39,6 +39,9 @@ def get_requires_for_build_wheel(config_settings=None): # Requirements to build an sdist are the same as for a wheel get_requires_for_build_sdist = get_requires_for_build_wheel +# Requirements to build an editable are the same as for a wheel +get_requires_for_build_editable = get_requires_for_build_wheel + def prepare_metadata_for_build_wheel(metadata_directory, config_settings=None): """Creates {metadata_directory}/foo-1.2.dist-info""" ini_info = read_flit_config(pyproj_toml) @@ -61,11 +64,19 @@ def prepare_metadata_for_build_wheel(metadata_directory, config_settings=None): return osp.basename(dist_info) +# Metadata for editable are the same as for a wheel +prepare_metadata_for_build_editable = prepare_metadata_for_build_wheel + def build_wheel(wheel_directory, config_settings=None, metadata_directory=None): """Builds a wheel, places it in wheel_directory""" info = make_wheel_in(pyproj_toml, Path(wheel_directory)) return info.file.name +def build_editable(wheel_directory, config_settings=None, metadata_directory=None): + """Builds an "editable" wheel, places it in wheel_directory""" + info = make_wheel_in(pyproj_toml, Path(wheel_directory), editable=True) + return info.file.name + def build_sdist(sdist_directory, config_settings=None): """Builds an sdist, places it in sdist_directory""" path = SdistBuilder.from_ini_path(pyproj_toml).build(Path(sdist_directory)) diff --git a/flit_core/flit_core/tests/test_buildapi.py b/flit_core/flit_core/tests/test_buildapi.py index 5cf519ea..3e621e69 100644 --- a/flit_core/flit_core/tests/test_buildapi.py +++ b/flit_core/flit_core/tests/test_buildapi.py @@ -24,6 +24,7 @@ def test_get_build_requires(): # importing it, so there are no build dependencies. with cwd(osp.join(samples_dir,'pep517')): assert buildapi.get_requires_for_build_wheel() == [] + assert buildapi.get_requires_for_build_editable() == [] assert buildapi.get_requires_for_build_sdist() == [] def test_get_build_requires_pep621_nodynamic(): @@ -31,6 +32,7 @@ def test_get_build_requires_pep621_nodynamic(): # as static metadata in pyproject.toml, so there are no build dependencies with cwd(osp.join(samples_dir, 'pep621_nodynamic')): assert buildapi.get_requires_for_build_wheel() == [] + assert buildapi.get_requires_for_build_editable() == [] assert buildapi.get_requires_for_build_sdist() == [] def test_get_build_requires_import(): @@ -39,6 +41,7 @@ def test_get_build_requires_import(): expected = ["numpy >=1.16.0"] with cwd(osp.join(samples_dir, 'constructed_version')): assert buildapi.get_requires_for_build_wheel() == expected + assert buildapi.get_requires_for_build_editable() == expected assert buildapi.get_requires_for_build_sdist() == expected def test_build_wheel(): @@ -47,6 +50,9 @@ def test_build_wheel(): assert filename.endswith('.whl'), filename assert_isfile(osp.join(td, filename)) assert zipfile.is_zipfile(osp.join(td, filename)) + with zipfile.ZipFile(osp.join(td, filename)) as zip: + assert "module1.py" in zip.namelist() + assert "module1.pth" not in zip.namelist() def test_build_wheel_pep621(): with TemporaryDirectory() as td, cwd(osp.join(samples_dir, 'pep621')): @@ -55,6 +61,16 @@ def test_build_wheel_pep621(): assert_isfile(osp.join(td, filename)) assert zipfile.is_zipfile(osp.join(td, filename)) +def test_build_editable(): + with TemporaryDirectory() as td, cwd(osp.join(samples_dir,'pep517')): + filename = buildapi.build_editable(td) + assert filename.endswith('.whl'), filename + assert_isfile(osp.join(td, filename)) + assert zipfile.is_zipfile(osp.join(td, filename)) + with zipfile.ZipFile(osp.join(td, filename)) as zip: + assert "module1.py" not in zip.namelist() + assert "module1.pth" in zip.namelist() + def test_build_sdist(): with TemporaryDirectory() as td, cwd(osp.join(samples_dir,'pep517')): filename = buildapi.build_sdist(td) @@ -68,3 +84,10 @@ def test_prepare_metadata_for_build_wheel(): assert dirname.endswith('.dist-info'), dirname assert_isdir(osp.join(td, dirname)) assert_isfile(osp.join(td, dirname, 'METADATA')) + +def test_prepare_metadata_for_build_editable(): + with TemporaryDirectory() as td, cwd(osp.join(samples_dir,'pep517')): + dirname = buildapi.prepare_metadata_for_build_editable(td) + assert dirname.endswith('.dist-info'), dirname + assert_isdir(osp.join(td, dirname)) + assert_isfile(osp.join(td, dirname, 'METADATA')) diff --git a/flit_core/flit_core/wheel.py b/flit_core/flit_core/wheel.py index b3c9eef4..0579e03b 100644 --- a/flit_core/flit_core/wheel.py +++ b/flit_core/flit_core/wheel.py @@ -146,6 +146,10 @@ def copy_module(self): rel_path = osp.relpath(full_path, source_dir) self._add_file(full_path, rel_path) + def add_pth(self): + with self._write_to_zip(self.module.name + ".pth") as f: + f.write(str(self.module.source_dir)) + def write_metadata(self): log.info('Writing metadata files') @@ -173,22 +177,25 @@ def write_record(self): # RECORD itself is recorded with no hash or size f.write(self.dist_info + '/RECORD,,\n') - def build(self): + def build(self, editable=False): try: - self.copy_module() + if editable: + self.add_pth() + else: + self.copy_module() self.write_metadata() self.write_record() finally: self.wheel_zip.close() -def make_wheel_in(ini_path, wheel_directory): +def make_wheel_in(ini_path, wheel_directory, editable=False): # We don't know the final filename until metadata is loaded, so write to # a temporary_file, and rename it afterwards. (fd, temp_path) = tempfile.mkstemp(suffix='.whl', dir=str(wheel_directory)) try: with io.open(fd, 'w+b') as fp: wb = WheelBuilder.from_ini_path(ini_path, fp) - wb.build() + wb.build(editable) wheel_path = wheel_directory / wb.wheel_filename os.replace(temp_path, str(wheel_path)) diff --git a/tests/test_wheel.py b/tests/test_wheel.py index 4cd5c3ed..b574e810 100644 --- a/tests/test_wheel.py +++ b/tests/test_wheel.py @@ -25,11 +25,33 @@ def test_wheel_module(copy_sample): make_wheel_in(td / 'pyproject.toml', td) assert_isfile(td / 'module1-0.1-py2.py3-none-any.whl') +def test_editable_wheel_module(copy_sample): + td = copy_sample('module1_toml') + make_wheel_in(td / 'pyproject.toml', td, editable=True) + whl_file = td / 'module1-0.1-py2.py3-none-any.whl' + assert_isfile(whl_file) + with unpack(whl_file) as unpacked: + pth_path = Path(unpacked, 'module1.pth') + assert_isfile(pth_path) + assert pth_path.read_text() == str(td) + assert_isdir(Path(unpacked, 'module1-0.1.dist-info')) + def test_wheel_package(copy_sample): td = copy_sample('package1') make_wheel_in(td / 'pyproject.toml', td) assert_isfile(td / 'package1-0.1-py2.py3-none-any.whl') +def test_editable_wheel_package(copy_sample): + td = copy_sample('package1') + make_wheel_in(td / 'pyproject.toml', td, editable=True) + whl_file = td / 'package1-0.1-py2.py3-none-any.whl' + assert_isfile(whl_file) + with unpack(whl_file) as unpacked: + pth_path = Path(unpacked, 'package1.pth') + assert_isfile(pth_path) + assert pth_path.read_text() == str(td) + assert_isdir(Path(unpacked, 'package1-0.1.dist-info')) + def test_wheel_src_module(copy_sample): td = copy_sample('module3') make_wheel_in(td / 'pyproject.toml', td) @@ -41,6 +63,17 @@ def test_wheel_src_module(copy_sample): assert_isdir(Path(unpacked, 'module3-0.1.dist-info')) assert_isfile(Path(unpacked, 'module3-0.1.dist-info', 'LICENSE')) +def test_editable_wheel_src_module(copy_sample): + td = copy_sample('module3') + make_wheel_in(td / 'pyproject.toml', td, editable=True) + whl_file = td / 'module3-0.1-py2.py3-none-any.whl' + assert_isfile(whl_file) + with unpack(whl_file) as unpacked: + pth_path = Path(unpacked, 'module3.pth') + assert_isfile(pth_path) + assert pth_path.read_text() == str(td / "src") + assert_isdir(Path(unpacked, 'module3-0.1.dist-info')) + def test_wheel_src_package(copy_sample): td = copy_sample('package2') make_wheel_in(td / 'pyproject.toml', td) @@ -51,6 +84,18 @@ def test_wheel_src_package(copy_sample): print(os.listdir(unpacked)) assert_isfile(Path(unpacked, 'package2', '__init__.py')) +def test_editable_wheel_src_package(copy_sample): + td = copy_sample('package2') + make_wheel_in(td / 'pyproject.toml', td, editable=True) + whl_file = td / 'package2-0.1-py2.py3-none-any.whl' + assert_isfile(whl_file) + with unpack(whl_file) as unpacked: + pth_path = Path(unpacked, 'package2.pth') + assert_isfile(pth_path) + assert pth_path.read_text() == str(td / "src") + assert_isdir(Path(unpacked, 'package2-0.1.dist-info')) + + def test_dist_name(copy_sample): td = copy_sample('altdistname') make_wheel_in(td / 'pyproject.toml', td)