2
2
3
3
Implements the Distutils 'build_scripts' command."""
4
4
5
- import os , re
5
+ import os
6
+ import re
6
7
from stat import ST_MODE
7
8
from distutils import sysconfig
8
9
from distutils .core import Command
11
12
from distutils import log
12
13
import tokenize
13
14
14
- # check if Python is called on the first line with this expression
15
- first_line_re = re .compile (b'^#!.*python[0-9.]*([ \t ].*)?$' )
15
+ shebang_pattern = re .compile ('^#!.*python[0-9.]*([ \t ].*)?$' )
16
+ """
17
+ Pattern matching a Python interpreter indicated in first line of a script.
18
+ """
19
+
20
+ # for Setuptools compatibility
21
+ first_line_re = shebang_pattern
22
+
16
23
17
24
class build_scripts (Command ):
18
25
@@ -26,13 +33,11 @@ class build_scripts(Command):
26
33
27
34
boolean_options = ['force' ]
28
35
29
-
30
36
def initialize_options (self ):
31
37
self .build_dir = None
32
38
self .scripts = None
33
39
self .force = None
34
40
self .executable = None
35
- self .outfiles = None
36
41
37
42
def finalize_options (self ):
38
43
self .set_undefined_options ('build' ,
@@ -49,104 +54,117 @@ def run(self):
49
54
return
50
55
self .copy_scripts ()
51
56
52
-
53
57
def copy_scripts (self ):
54
- r"""Copy each script listed in 'self.scripts'; if it's marked as a
55
- Python script in the Unix way (first line matches 'first_line_re',
56
- ie. starts with "\#!" and contains "python"), then adjust the first
57
- line to refer to the current Python interpreter as we copy.
58
+ """
59
+ Copy each script listed in ``self.scripts``.
60
+
61
+ If a script is marked as a Python script (first line matches
62
+ 'shebang_pattern', i.e. starts with ``#!`` and contains
63
+ "python"), then adjust in the copy the first line to refer to
64
+ the current Python interpreter.
58
65
"""
59
66
self .mkpath (self .build_dir )
60
67
outfiles = []
61
68
updated_files = []
62
69
for script in self .scripts :
63
- adjust = False
64
- script = convert_path (script )
65
- outfile = os .path .join (self .build_dir , os .path .basename (script ))
66
- outfiles .append (outfile )
67
-
68
- if not self .force and not newer (script , outfile ):
69
- log .debug ("not copying %s (up-to-date)" , script )
70
- continue
71
-
72
- # Always open the file, but ignore failures in dry-run mode --
73
- # that way, we'll get accurate feedback if we can read the
74
- # script.
75
- try :
76
- f = open (script , "rb" )
77
- except OSError :
78
- if not self .dry_run :
79
- raise
80
- f = None
81
- else :
82
- encoding , lines = tokenize .detect_encoding (f .readline )
83
- f .seek (0 )
84
- first_line = f .readline ()
85
- if not first_line :
86
- self .warn ("%s is an empty file (skipping)" % script )
87
- continue
88
-
89
- match = first_line_re .match (first_line )
90
- if match :
91
- adjust = True
92
- post_interp = match .group (1 ) or b''
93
-
94
- if adjust :
95
- log .info ("copying and adjusting %s -> %s" , script ,
96
- self .build_dir )
97
- updated_files .append (outfile )
98
- if not self .dry_run :
99
- if not sysconfig .python_build :
100
- executable = self .executable
101
- else :
102
- executable = os .path .join (
103
- sysconfig .get_config_var ("BINDIR" ),
104
- "python%s%s" % (sysconfig .get_config_var ("VERSION" ),
105
- sysconfig .get_config_var ("EXE" )))
106
- executable = os .fsencode (executable )
107
- shebang = b"#!" + executable + post_interp + b"\n "
108
- # Python parser starts to read a script using UTF-8 until
109
- # it gets a #coding:xxx cookie. The shebang has to be the
110
- # first line of a file, the #coding:xxx cookie cannot be
111
- # written before. So the shebang has to be decodable from
112
- # UTF-8.
113
- try :
114
- shebang .decode ('utf-8' )
115
- except UnicodeDecodeError :
116
- raise ValueError (
117
- "The shebang ({!r}) is not decodable "
118
- "from utf-8" .format (shebang ))
119
- # If the script is encoded to a custom encoding (use a
120
- # #coding:xxx cookie), the shebang has to be decodable from
121
- # the script encoding too.
122
- try :
123
- shebang .decode (encoding )
124
- except UnicodeDecodeError :
125
- raise ValueError (
126
- "The shebang ({!r}) is not decodable "
127
- "from the script encoding ({})"
128
- .format (shebang , encoding ))
129
- with open (outfile , "wb" ) as outf :
130
- outf .write (shebang )
131
- outf .writelines (f .readlines ())
132
- if f :
133
- f .close ()
134
- else :
135
- if f :
136
- f .close ()
137
- updated_files .append (outfile )
138
- self .copy_file (script , outfile )
139
-
140
- if os .name == 'posix' :
141
- for file in outfiles :
142
- if self .dry_run :
143
- log .info ("changing mode of %s" , file )
144
- else :
145
- oldmode = os .stat (file )[ST_MODE ] & 0o7777
146
- newmode = (oldmode | 0o555 ) & 0o7777
147
- if newmode != oldmode :
148
- log .info ("changing mode of %s from %o to %o" ,
149
- file , oldmode , newmode )
150
- os .chmod (file , newmode )
151
- # XXX should we modify self.outfiles?
70
+ self ._copy_script (script , outfiles , updated_files )
71
+
72
+ self ._change_modes (outfiles )
73
+
152
74
return outfiles , updated_files
75
+
76
+ def _copy_script (self , script , outfiles , updated_files ):
77
+ shebang_match = None
78
+ script = convert_path (script )
79
+ outfile = os .path .join (self .build_dir , os .path .basename (script ))
80
+ outfiles .append (outfile )
81
+
82
+ if not self .force and not newer (script , outfile ):
83
+ log .debug ("not copying %s (up-to-date)" , script )
84
+ return
85
+
86
+ # Always open the file, but ignore failures in dry-run mode
87
+ # in order to attempt to copy directly.
88
+ try :
89
+ f = tokenize .open (script )
90
+ except OSError :
91
+ if not self .dry_run :
92
+ raise
93
+ f = None
94
+ else :
95
+ first_line = f .readline ()
96
+ if not first_line :
97
+ self .warn ("%s is an empty file (skipping)" % script )
98
+ return
99
+
100
+ shebang_match = shebang_pattern .match (first_line )
101
+
102
+ updated_files .append (outfile )
103
+ if shebang_match :
104
+ log .info ("copying and adjusting %s -> %s" , script ,
105
+ self .build_dir )
106
+ if not self .dry_run :
107
+ if not sysconfig .python_build :
108
+ executable = self .executable
109
+ else :
110
+ executable = os .path .join (
111
+ sysconfig .get_config_var ("BINDIR" ),
112
+ "python%s%s" % (
113
+ sysconfig .get_config_var ("VERSION" ),
114
+ sysconfig .get_config_var ("EXE" )))
115
+ post_interp = shebang_match .group (1 ) or ''
116
+ shebang = "#!" + executable + post_interp + "\n "
117
+ self ._validate_shebang (shebang , f .encoding )
118
+ with open (outfile , "w" , encoding = f .encoding ) as outf :
119
+ outf .write (shebang )
120
+ outf .writelines (f .readlines ())
121
+ if f :
122
+ f .close ()
123
+ else :
124
+ if f :
125
+ f .close ()
126
+ self .copy_file (script , outfile )
127
+
128
+ def _change_modes (self , outfiles ):
129
+ if os .name != 'posix' :
130
+ return
131
+
132
+ for file in outfiles :
133
+ self ._change_mode (file )
134
+
135
+ def _change_mode (self , file ):
136
+ if self .dry_run :
137
+ log .info ("changing mode of %s" , file )
138
+ return
139
+
140
+ oldmode = os .stat (file )[ST_MODE ] & 0o7777
141
+ newmode = (oldmode | 0o555 ) & 0o7777
142
+ if newmode != oldmode :
143
+ log .info ("changing mode of %s from %o to %o" ,
144
+ file , oldmode , newmode )
145
+ os .chmod (file , newmode )
146
+
147
+ @staticmethod
148
+ def _validate_shebang (shebang , encoding ):
149
+ # Python parser starts to read a script using UTF-8 until
150
+ # it gets a #coding:xxx cookie. The shebang has to be the
151
+ # first line of a file, the #coding:xxx cookie cannot be
152
+ # written before. So the shebang has to be encodable to
153
+ # UTF-8.
154
+ try :
155
+ shebang .encode ('utf-8' )
156
+ except UnicodeEncodeError :
157
+ raise ValueError (
158
+ "The shebang ({!r}) is not encodable "
159
+ "to utf-8" .format (shebang ))
160
+
161
+ # If the script is encoded to a custom encoding (use a
162
+ # #coding:xxx cookie), the shebang has to be encodable to
163
+ # the script encoding too.
164
+ try :
165
+ shebang .encode (encoding )
166
+ except UnicodeEncodeError :
167
+ raise ValueError (
168
+ "The shebang ({!r}) is not encodable "
169
+ "to the script encoding ({})"
170
+ .format (shebang , encoding ))
0 commit comments