@@ -1567,50 +1567,79 @@ def get_exe_prefixes(exe_filename):
1567
1567
class PthDistributions (Environment ):
1568
1568
"""A .pth file with Distribution paths in it"""
1569
1569
1570
- dirty = False
1571
-
1572
1570
def __init__ (self , filename , sitedirs = ()):
1573
1571
self .filename = filename
1574
1572
self .sitedirs = list (map (normalize_path , sitedirs ))
1575
1573
self .basedir = normalize_path (os .path .dirname (self .filename ))
1576
- self ._load ()
1574
+ self .paths , self .dirty = self ._load ()
1575
+ # keep a copy if someone manually updates the paths attribute on the instance
1576
+ self ._init_paths = self .paths [:]
1577
1577
super ().__init__ ([], None , None )
1578
1578
for path in yield_lines (self .paths ):
1579
1579
list (map (self .add , find_distributions (path , True )))
1580
1580
1581
- def _load (self ):
1582
- self . paths = []
1583
- saw_import = False
1581
+ def _load_raw (self ):
1582
+ paths = []
1583
+ dirty = saw_import = False
1584
1584
seen = dict .fromkeys (self .sitedirs )
1585
- if os .path .isfile (self .filename ):
1586
- f = open (self .filename , 'rt' )
1587
- for line in f :
1588
- if line .startswith ('import' ):
1589
- saw_import = True
1590
- continue
1591
- path = line .rstrip ()
1592
- self .paths .append (path )
1593
- if not path .strip () or path .strip ().startswith ('#' ):
1594
- continue
1595
- # skip non-existent paths, in case somebody deleted a package
1596
- # manually, and duplicate paths as well
1597
- path = self .paths [- 1 ] = normalize_path (
1598
- os .path .join (self .basedir , path )
1599
- )
1600
- if not os .path .exists (path ) or path in seen :
1601
- self .paths .pop () # skip it
1602
- self .dirty = True # we cleaned up, so we're dirty now :)
1603
- continue
1604
- seen [path ] = 1
1605
- f .close ()
1585
+ f = open (self .filename , 'rt' )
1586
+ for line in f :
1587
+ path = line .rstrip ()
1588
+ # still keep imports and empty/commented lines for formatting
1589
+ paths .append (path )
1590
+ if line .startswith (('import ' , 'from ' )):
1591
+ saw_import = True
1592
+ continue
1593
+ stripped_path = path .strip ()
1594
+ if not stripped_path or stripped_path .startswith ('#' ):
1595
+ continue
1596
+ # skip non-existent paths, in case somebody deleted a package
1597
+ # manually, and duplicate paths as well
1598
+ normalized_path = normalize_path (os .path .join (self .basedir , path ))
1599
+ if normalized_path in seen or not os .path .exists (normalized_path ):
1600
+ log .debug ("cleaned up dirty or duplicated %r" , path )
1601
+ dirty = True
1602
+ paths .pop ()
1603
+ continue
1604
+ seen [normalized_path ] = 1
1605
+ f .close ()
1606
+ # remove any trailing empty/blank line
1607
+ while paths and not paths [- 1 ].strip ():
1608
+ paths .pop ()
1609
+ dirty = True
1610
+ return paths , dirty or (paths and saw_import )
1606
1611
1607
- if self . paths and not saw_import :
1608
- self .dirty = True # ensure anything we touch has import wrappers
1609
- while self . paths and not self .paths [ - 1 ]. strip ():
1610
- self . paths . pop ()
1612
+ def _load ( self ) :
1613
+ if os . path . isfile ( self .filename ):
1614
+ return self ._load_raw ()
1615
+ return [], False
1611
1616
1612
1617
def save (self ):
1613
1618
"""Write changed .pth file back to disk"""
1619
+ # first reload the file
1620
+ last_paths , last_dirty = self ._load ()
1621
+ # and check that there are no difference with what we have.
1622
+ # there can be difference if someone else has written to the file
1623
+ # since we first loaded it.
1624
+ # we don't want to lose the eventual new paths added since then.
1625
+ for path in last_paths [:]:
1626
+ if path not in self .paths :
1627
+ self .paths .append (path )
1628
+ log .info ("detected new path %r" , path )
1629
+ last_dirty = True
1630
+ else :
1631
+ last_paths .remove (path )
1632
+ # also, re-check that all paths are still valid before saving them
1633
+ for path in self .paths [:]:
1634
+ if path not in last_paths \
1635
+ and not path .startswith (('import ' , 'from ' , '#' )):
1636
+ absolute_path = os .path .join (self .basedir , path )
1637
+ if not os .path .exists (absolute_path ):
1638
+ self .paths .remove (path )
1639
+ log .info ("removing now non-existent path %r" , path )
1640
+ last_dirty = True
1641
+
1642
+ self .dirty |= last_dirty or self .paths != self ._init_paths
1614
1643
if not self .dirty :
1615
1644
return
1616
1645
@@ -1619,17 +1648,16 @@ def save(self):
1619
1648
log .debug ("Saving %s" , self .filename )
1620
1649
lines = self ._wrap_lines (rel_paths )
1621
1650
data = '\n ' .join (lines ) + '\n '
1622
-
1623
1651
if os .path .islink (self .filename ):
1624
1652
os .unlink (self .filename )
1625
1653
with open (self .filename , 'wt' ) as f :
1626
1654
f .write (data )
1627
-
1628
1655
elif os .path .exists (self .filename ):
1629
1656
log .debug ("Deleting empty %s" , self .filename )
1630
1657
os .unlink (self .filename )
1631
1658
1632
1659
self .dirty = False
1660
+ self ._init_paths [:] = self .paths [:]
1633
1661
1634
1662
@staticmethod
1635
1663
def _wrap_lines (lines ):
0 commit comments