30
30
31
31
logger = logging .getLogger (__name__ )
32
32
33
+ PEP517_META_BUILD = """\
34
+ import pep517.build
35
+ import pep517.meta
36
+
37
+ path='{source}'
38
+ system=pep517.build.compat_system(path)
39
+ pep517.meta.build(source_dir=path, dest='{dest}', system=system)
40
+ """
41
+
42
+ PEP517_META_BUILD_DEPS = ["pep517===0.8.2" , "toml==0.10.1" ]
43
+
33
44
34
45
class PackageInfoError (ValueError ):
35
46
def __init__ (self , path ): # type: (Union[Path, str]) -> None
@@ -256,17 +267,27 @@ def _from_sdist_file(cls, path): # type: (Path) -> PackageInfo
256
267
257
268
return info .update (new_info )
258
269
270
+ @staticmethod
271
+ def has_setup_files (path ): # type: (Path) -> bool
272
+ return any ((path / f ).exists () for f in SetupReader .FILES )
273
+
259
274
@classmethod
260
- def from_setup_py (cls , path ): # type: (Union[str, Path] ) -> PackageInfo
275
+ def from_setup_files (cls , path ): # type: (Path) -> PackageInfo
261
276
"""
262
- Mechanism to parse package information from a `setup.py ` file. This uses the implentation
277
+ Mechanism to parse package information from a `setup.[py|cfg] ` file. This uses the implementation
263
278
at `poetry.utils.setup_reader.SetupReader` in order to parse the file. This is not reliable for
264
279
complex setup files and should only attempted as a fallback.
265
280
266
281
:param path: Path to `setup.py` file
267
- :return:
268
282
"""
269
- result = SetupReader .read_from_directory (Path (path ))
283
+ if not cls .has_setup_files (path ):
284
+ raise PackageInfoError (path )
285
+
286
+ try :
287
+ result = SetupReader .read_from_directory (path )
288
+ except Exception :
289
+ raise PackageInfoError (path )
290
+
270
291
python_requires = result ["python_requires" ]
271
292
if python_requires is None :
272
293
python_requires = "*"
@@ -288,14 +309,20 @@ def from_setup_py(cls, path): # type: (Union[str, Path]) -> PackageInfo
288
309
289
310
requirements = parse_requires (requires )
290
311
291
- return cls (
312
+ info = cls (
292
313
name = result .get ("name" ),
293
314
version = result .get ("version" ),
294
315
summary = result .get ("description" , "" ),
295
316
requires_dist = requirements or None ,
296
317
requires_python = python_requires ,
297
318
)
298
319
320
+ if not (info .name and info .version ) and not info .requires_dist :
321
+ # there is nothing useful here
322
+ raise PackageInfoError (path )
323
+
324
+ return info
325
+
299
326
@staticmethod
300
327
def _find_dist_info (path ): # type: (Path) -> Iterator[Path]
301
328
"""
@@ -308,22 +335,20 @@ def _find_dist_info(path): # type: (Path) -> Iterator[Path]
308
335
# Sometimes pathlib will fail on recursive symbolic links, so we need to workaround it
309
336
# and use the glob module instead. Note that this does not happen with pathlib2
310
337
# so it's safe to use it for Python < 3.4.
311
- directories = glob .iglob (Path ( path , pattern ).as_posix (), recursive = True )
338
+ directories = glob .iglob (path . joinpath ( pattern ).as_posix (), recursive = True )
312
339
else :
313
340
directories = path .glob (pattern )
314
341
315
342
for d in directories :
316
343
yield Path (d )
317
344
318
345
@classmethod
319
- def from_metadata (cls , path ): # type: (Union[str, Path] ) -> Optional[PackageInfo]
346
+ def from_metadata (cls , path ): # type: (Path) -> Optional[PackageInfo]
320
347
"""
321
348
Helper method to parse package information from an unpacked metadata directory.
322
349
323
350
:param path: The metadata directory to parse information from.
324
351
"""
325
- path = Path (path )
326
-
327
352
if path .suffix in {".dist-info" , ".egg-info" }:
328
353
directories = [path ]
329
354
else :
@@ -392,10 +417,79 @@ def _get_poetry_package(path): # type: (Path) -> Optional[ProjectPackage]
392
417
except RuntimeError :
393
418
pass
394
419
420
+ @classmethod
421
+ def _pep517_metadata (cls , path ): # type (Path) -> PackageInfo
422
+ """
423
+ Helper method to use PEP-517 library to build and read package metadata.
424
+
425
+ :param path: Path to package source to build and read metadata for.
426
+ """
427
+ info = None
428
+ try :
429
+ info = cls .from_setup_files (path )
430
+ if info .requires_dist is not None :
431
+ return info
432
+ except PackageInfoError :
433
+ pass
434
+
435
+ with temporary_directory () as tmp_dir :
436
+ # TODO: cache PEP 517 build environment corresponding to each project venv
437
+ venv_dir = Path (tmp_dir ) / ".venv"
438
+ EnvManager .build_venv (venv_dir .as_posix ())
439
+ venv = VirtualEnv (venv_dir , venv_dir )
440
+
441
+ dest_dir = Path (tmp_dir ) / "dist"
442
+ dest_dir .mkdir ()
443
+
444
+ try :
445
+ venv .run (
446
+ "python" ,
447
+ "-m" ,
448
+ "pip" ,
449
+ "install" ,
450
+ "--disable-pip-version-check" ,
451
+ "--ignore-installed" ,
452
+ * PEP517_META_BUILD_DEPS
453
+ )
454
+ venv .run (
455
+ "python" ,
456
+ "-" ,
457
+ input_ = PEP517_META_BUILD .format (
458
+ source = path .as_posix (), dest = dest_dir .as_posix ()
459
+ ),
460
+ )
461
+ return cls .from_metadata (dest_dir )
462
+ except EnvCommandError as e :
463
+ # something went wrong while attempting pep517 metadata build
464
+ # fallback to egg_info if setup.py available
465
+ cls ._log ("PEP517 build failed: {}" .format (e ), level = "debug" )
466
+ setup_py = path / "setup.py"
467
+ if not setup_py .exists ():
468
+ raise PackageInfoError (path )
469
+
470
+ cwd = Path .cwd ()
471
+ os .chdir (path .as_posix ())
472
+ try :
473
+ venv .run ("python" , "setup.py" , "egg_info" )
474
+ return cls .from_metadata (path )
475
+ except EnvCommandError :
476
+ raise PackageInfoError (path )
477
+ finally :
478
+ os .chdir (cwd .as_posix ())
479
+
480
+ if info :
481
+ cls ._log (
482
+ "Falling back to parsed setup.py file for {}" .format (path ), "debug"
483
+ )
484
+ return info
485
+
486
+ # if we reach here, everything has failed and all hope is lost
487
+ raise PackageInfoError (path )
488
+
395
489
@classmethod
396
490
def from_directory (
397
491
cls , path , allow_build = False
398
- ): # type: (Union[str, Path] , bool) -> PackageInfo
492
+ ): # type: (Path, bool) -> PackageInfo
399
493
"""
400
494
Generate package information from a package source directory. When `allow_build` is enabled and
401
495
introspection of all available metadata fails, the package is attempted to be build in an isolated
@@ -404,57 +498,28 @@ def from_directory(
404
498
:param path: Path to generate package information from.
405
499
:param allow_build: If enabled, as a fallback, build the project to gather metadata.
406
500
"""
407
- path = Path (path )
408
-
409
- current_dir = os .getcwd ()
410
-
411
501
info = cls .from_metadata (path )
412
502
413
503
if info and info .requires_dist is not None :
414
504
# return only if requirements are discovered
415
505
return info
416
506
417
- setup_py = path .joinpath ("setup.py" )
418
-
419
507
project_package = cls ._get_poetry_package (path )
420
508
if project_package :
421
509
return cls .from_package (project_package )
422
510
423
- if not setup_py .exists ():
424
- if not allow_build and info :
425
- # we discovered PkgInfo but no requirements were listed
426
- return info
427
- # this means we cannot do anything else here
428
- raise PackageInfoError (path )
429
-
430
- if not allow_build :
431
- return cls .from_setup_py (path = path )
432
-
433
511
try :
434
- # TODO: replace with PEP517
435
- # we need to switch to the correct path in order for egg_info command to work
436
- os .chdir (str (path ))
437
-
438
- # Execute egg_info
439
- cls ._execute_setup ()
440
- except EnvCommandError :
441
- cls ._log (
442
- "Falling back to parsing setup.py file for {}" .format (path ), "debug"
443
- )
444
- # egg_info could not be generated, we fallback to ast parser
445
- return cls .from_setup_py (path = path )
446
- else :
447
- info = cls .from_metadata (path )
512
+ if not allow_build :
513
+ return cls .from_setup_files (path )
514
+ return cls ._pep517_metadata (path )
515
+ except PackageInfoError as e :
448
516
if info :
517
+ # we discovered PkgInfo but no requirements were listed
449
518
return info
450
- finally :
451
- os .chdir (current_dir )
452
-
453
- # if we reach here, everything has failed and all hope is lost
454
- raise PackageInfoError (path )
519
+ raise e
455
520
456
521
@classmethod
457
- def from_sdist (cls , path ): # type: (Union[ Path, pkginfo.SDist] ) -> PackageInfo
522
+ def from_sdist (cls , path ): # type: (Path) -> PackageInfo
458
523
"""
459
524
Gather package information from an sdist file, packed or unpacked.
460
525
@@ -508,10 +573,3 @@ def from_path(cls, path): # type: (Path) -> PackageInfo
508
573
return cls .from_bdist (path = path )
509
574
except PackageInfoError :
510
575
return cls .from_sdist (path = path )
511
-
512
- @classmethod
513
- def _execute_setup (cls ):
514
- with temporary_directory () as tmp_dir :
515
- EnvManager .build_venv (tmp_dir )
516
- venv = VirtualEnv (Path (tmp_dir ), Path (tmp_dir ))
517
- venv .run ("python" , "setup.py" , "egg_info" )
0 commit comments