forked from weewx/weewx
-
Notifications
You must be signed in to change notification settings - Fork 0
/
setup.py
executable file
·336 lines (278 loc) · 12.8 KB
/
setup.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
#!/usr/bin/env python
#
# weewx --- A simple, high-performance weather station server
#
# Copyright (c) 2009-2020 Tom Keffer <[email protected]>
#
# See the file LICENSE.txt for your full rights.
#
"""Customized setup file for weewx.
For more debug information, set the environment variable DISTUTILS_DEBUG
before running.
"""
from __future__ import absolute_import
from __future__ import print_function
from __future__ import with_statement
import fnmatch
import os.path
import re
import shutil
import subprocess
import sys
import tempfile
from distutils import log
from distutils.command.install import install
from distutils.command.install_data import install_data
from distutils.command.install_lib import install_lib
from distutils.core import setup
from distutils.debug import DEBUG
VERSION = "4.0.0"
if sys.version_info < (2, 7):
log.fatal('WeeWX requires Python V2.7 or greater.')
log.fatal('For earlier versions of Python, use WeeWX V3.9.')
sys.exit("Python version unsupported.")
this_file = os.path.join(os.getcwd(), __file__)
this_dir = os.path.abspath(os.path.dirname(this_file))
# ==============================================================================
# install
# ==============================================================================
class weewx_install(install):
"""Specialized version of install, which adds a '--no-prompt' option, and which runs a
wee_config post-install script"""
# Add an option for --no-prompt. This will be passed on to wee_config
user_options = install.user_options + [('no-prompt', None, 'Do not prompt for station info')]
def initialize_options(self, *args, **kwargs):
install.initialize_options(self, *args, **kwargs)
self.no_prompt = None
def finalize_options(self):
# Call my superclass's version
install.finalize_options(self)
# Unless the --force flag has been explicitly set, default to True. This will
# cause files to be installed even if they are older than their target."""
if self.force is None:
self.force = 1
if self.no_prompt is None:
self.no_prompt = 0
def run(self):
"""Specialized version of run, which runs post-install commands"""
# First run the install.
rv = install.run(self)
# Now the post-install
update_and_install_config(self.install_data, self.install_scripts, self.install_lib,
self.no_prompt)
return rv
# ==============================================================================
# install_lib
# ==============================================================================
class weewx_install_lib(install_lib):
"""Specialized version of install_lib, which saves and restores the 'user' subdirectory."""
def run(self):
"""Specialized version of run that saves, then restores, the 'user' subdirectory."""
# Save any existing 'user' subdirectory:
user_dir = os.path.join(self.install_dir, 'user')
if not self.dry_run and os.path.exists(user_dir):
user_backup_dir = user_dir + ".bak"
shutil.move(user_dir, user_backup_dir)
else:
user_backup_dir = None
# Run the superclass's version. This will install a new 'user' subdirectory.
install_lib.run(self)
# Restore the 'user' subdirectory
if user_backup_dir:
# Delete the freshly installed user subdirectory
shutil.rmtree(user_dir)
# Replace it with our saved version.
shutil.move(user_backup_dir, user_dir)
# ==============================================================================
# install_data
# ==============================================================================
class weewx_install_data(install_data):
"""Specialized version of install_data."""
def run(self):
# If there is a skins directory already, just install what the user doesn't already have.
if os.path.exists(os.path.join(self.install_dir, 'skins')):
# A skins directory already exists. Build a list of skins that are missing and should
# be added to it.
install_files = []
for skin_name in ['Ftp', 'Mobile', 'Rsync', 'Seasons', 'Smartphone', 'Standard']:
rel_name = 'skins/' + skin_name
if not os.path.exists(os.path.join(self.install_dir, rel_name)):
# The skin has not already been installed. Include it.
install_files += [dat for dat in self.data_files if
dat[0].startswith(rel_name)]
# Exclude all the skins files...
other_files = [dat for dat in self.data_files if not dat[0].startswith('skins')]
# ... then add the needed skins back in
self.data_files = other_files + install_files
# Run the superclass's run():
return install_data.run(self)
def copy_file(self, f, install_dir, **kwargs):
# If this is the configuration file, then process it separately
if f == 'weewx.conf':
rv = self.process_config_file(f, install_dir, **kwargs)
else:
rv = install_data.copy_file(self, f, install_dir, **kwargs)
return rv
def process_config_file(self, f, install_dir, **kwargs):
"""Process the configuration file weewx.conf by inserting the proper path into WEEWX_ROOT,
then install as weewx.conf.X.Y.Z, where X.Y.Z is the version number."""
# The install directory. The normalization is necessary because sometimes there
# is a trailing '/'.
norm_install_dir = os.path.normpath(install_dir)
# The path to the destination configuration file. It will look
# something like '/home/weewx/weewx.conf.4.0.0'
weewx_install_path = os.path.join(norm_install_dir, os.path.basename(f) + '.' + VERSION)
if self.dry_run:
return weewx_install_path, 0
# This RE is for finding the assignment to WEEWX_ROOT.
# It matches the assignment, plus an optional comment. For example, for the string
# ' WEEWX_ROOT = foo # A comment'
# it matches 3 groups:
# Group 1: ' WEEWX_ROOT '
# Group 2: ' foo'
# Group 3: ' # A comment'
pattern = re.compile(r'(^\s*WEEWX_ROOT\s*)=(\s*\S+)(\s*#?.*)')
done = 0
if self.verbose:
log.info("massaging %s -> %s", f, weewx_install_path)
# Massage the incoming file, assigning the right value for WEEWX_ROOT. Use a temporary
# file. This will help in making the operatioin atomic.
try:
# Open up the incoming configuration file
with open(f, mode='rt') as incoming_fd:
# Open up a temporary file
tmpfd, tmpfn = tempfile.mkstemp()
with os.fdopen(tmpfd, 'wt') as tmpfile:
# Go through the incoming template file line by line, inserting a value for
# WEEWX_ROOT. There's only one per file, so stop looking after the first one.
for line in incoming_fd:
if not done:
line, done = pattern.subn("\\1 = %s\\3" % norm_install_dir, line)
tmpfile.write(line)
if not self.dry_run:
# If not a dry run, install the temporary file in the right spot
rv = install_data.copy_file(self, tmpfn, weewx_install_path, **kwargs)
# Set the permission bits:
shutil.copymode(f, weewx_install_path)
finally:
# Get rid of the temporary file
os.remove(tmpfn)
return rv
# ==============================================================================
# utilities
# ==============================================================================
def find_files(directory, file_excludes=['*.pyc', "junk*"], dir_excludes=['*/__pycache__']):
"""Find all files under a directory, honoring some exclusions.
Returns:
A list of two-way tuples (directory_path, file_list), where file_list is a list
of relative file paths.
"""
# First recursively create a list of all the directories
dir_list = []
for dirpath, _, _ in os.walk(directory):
# Make sure the directory name doesn't match the excluded pattern
if not any(fnmatch.fnmatch(dirpath, d) for d in dir_excludes):
dir_list.append(dirpath)
data_files = []
# Now search each directory for all files
for d_path in dir_list:
file_list = []
# Find all the files in this directory
for fn in os.listdir(d_path):
filepath = os.path.join(d_path, fn)
# Make sure it's a file, and that it's name doesn't match the excluded pattern
if os.path.isfile(filepath) \
and not any(fnmatch.fnmatch(filepath, f) for f in file_excludes):
file_list.append(filepath)
# Add the directory and the list of files in it, to the list of all files.
data_files.append((d_path, file_list))
return data_files
def update_and_install_config(install_dir, install_scripts, install_lib, no_prompt=False,
config_name='weewx.conf'):
"""Install the configuration file, weewx.conf, updating it if necessary.
install_dir: the directory containing the configuration file.
install_scripts: the directory containing the weewx executables.
install_lib: the directory containing the weewx packages.
no_prompt: Pass a '--no-prompt' flag on to wee_config.
config_name: the name of the configuration file. Defaults to 'weewx.conf'
"""
# This is where the weewx.conf file will go
config_path = os.path.join(install_dir, config_name)
# Is there an existing file?
if os.path.isfile(config_path):
# Yes, so this is an upgrade
args = [sys.executable,
os.path.join(install_scripts, 'wee_config'),
'--upgrade',
'--config=%s' % config_path,
'--dist-config=%s' % config_path + '.' + VERSION,
'--output=%s' % config_path,
]
else:
# No existing config file, so this is a fresh install.
args = [sys.executable,
os.path.join(install_scripts, 'wee_config'),
'--install',
'--dist-config=%s' % config_path + '.' + VERSION,
'--output=%s' % config_path,
]
# Add the --no-prompt flag if the user requested it.
if no_prompt:
args += ['--no-prompt']
if DEBUG:
log.info("Command used to invoke wee_config: %s" % args)
proc = subprocess.Popen(args,
env={'PYTHONPATH': install_lib},
stdin=sys.stdin,
stdout=sys.stdout,
stderr=sys.stderr)
out, err = proc.communicate()
if DEBUG and out:
log.info('out=', out.decode())
if DEBUG and err:
log.info('err=', err.decode())
# ==============================================================================
# main entry point
# ==============================================================================
if __name__ == "__main__":
setup(name='weewx',
version=VERSION,
description='The WeeWX weather software system',
long_description="WeeWX interacts with a weather station to produce graphs, reports, "
"and HTML pages. WeeWX can upload data to services such as the "
"WeatherUnderground, PWSweather.com, or CWOP.",
author='Tom Keffer',
author_email='[email protected]',
url='http://www.weewx.com',
license='GPLv3',
py_modules=['daemon', 'six'],
package_dir={'': 'bin'},
packages=['schemas',
'user',
'weecfg',
'weedb',
'weeimport',
'weeplot',
'weeutil',
'weewx',
'weewx.drivers'],
scripts=['bin/wee_config',
'bin/wee_database',
'bin/wee_debug',
'bin/wee_device',
'bin/wee_extension',
'bin/wee_import',
'bin/wee_reports',
'bin/weewxd',
'bin/wunderfixer'],
data_files=[('', ['LICENSE.txt', 'README.md', 'weewx.conf']), ]
+ find_files('docs')
+ find_files('examples')
+ find_files('skins')
+ find_files('util'),
cmdclass={
"install": weewx_install,
"install_data": weewx_install_data,
"install_lib": weewx_install_lib,
},
)