1
- #!/usr/bin/env python3
2
1
from __future__ import annotations
3
2
4
- import hashlib
5
- import http
6
- import io
7
- import os .path
8
- import platform
9
- import stat
10
- import sys
11
- import tarfile
12
- import urllib .request
13
- import zipfile
14
-
15
- from distutils .command .build import build as orig_build
16
- from distutils .core import Command
17
3
from setuptools import setup
18
- from setuptools .command .install import install as orig_install
19
-
20
- SHELLCHECK_VERSION = '0.9.0'
21
- POSTFIX_SHA256 = {
22
- ('linux' , 'armv6hf' ): (
23
- 'linux.armv6hf.tar.xz' ,
24
- '03deed9ded9dd66434ccf9649815bcde7d275d6c9f6dcf665b83391673512c75' ,
25
- ),
26
- ('linux' , 'aarch64' ): (
27
- 'linux.aarch64.tar.xz' ,
28
- '179c579ef3481317d130adebede74a34dbbc2df961a70916dd4039ebf0735fae' ,
29
- ),
30
- ('linux' , 'x86_64' ): (
31
- 'linux.x86_64.tar.xz' ,
32
- '700324c6dd0ebea0117591c6cc9d7350d9c7c5c287acbad7630fa17b1d4d9e2f' ,
33
- ),
34
- ('darwin' , 'x86_64' ): (
35
- 'darwin.x86_64.tar.xz' ,
36
- '7d3730694707605d6e60cec4efcb79a0632d61babc035aa16cda1b897536acf5' ,
37
- ),
38
- ('win32' , 'AMD64' ): (
39
- 'zip' ,
40
- 'ae58191b1ea4ffd9e5b15da9134146e636440302ce3e2f46863e8d71c8be1bbb' ,
41
- ),
42
- }
43
- POSTFIX_SHA256 [('cygwin' , 'x86_64' )] = POSTFIX_SHA256 [('win32' , 'AMD64' )]
44
- POSTFIX_SHA256 [('win32' , 'ARM64' )] = POSTFIX_SHA256 [('win32' , 'AMD64' )]
45
- POSTFIX_SHA256 [('darwin' , 'arm64' )] = POSTFIX_SHA256 [('darwin' , 'x86_64' )]
46
- POSTFIX_SHA256 [('linux' , 'armv7l' )] = POSTFIX_SHA256 [('linux' , 'armv6hf' )]
47
- PY_VERSION = '6'
48
-
49
-
50
- def get_download_url () -> tuple [str , str ]:
51
- postfix , sha256 = POSTFIX_SHA256 [(sys .platform , platform .machine ())]
52
- url = (
53
- f'https://github.com/koalaman/shellcheck/releases/download/'
54
- f'v{ SHELLCHECK_VERSION } /shellcheck-v{ SHELLCHECK_VERSION } .{ postfix } '
55
- )
56
- return url , sha256
57
-
58
-
59
- def download (url : str , sha256 : str ) -> bytes :
60
- with urllib .request .urlopen (url ) as resp :
61
- code = resp .getcode ()
62
- if code != http .HTTPStatus .OK :
63
- raise ValueError (f'HTTP failure. Code: { code } ' )
64
- data = resp .read ()
65
-
66
- checksum = hashlib .sha256 (data ).hexdigest ()
67
- if checksum != sha256 :
68
- raise ValueError (f'sha256 mismatch, expected { sha256 } , got { checksum } ' )
69
-
70
- return data
71
-
72
-
73
- def extract (url : str , data : bytes ) -> bytes :
74
- with io .BytesIO (data ) as bio :
75
- if '.tar.' in url :
76
- with tarfile .open (fileobj = bio ) as tarf :
77
- for info in tarf .getmembers ():
78
- if info .isfile () and info .name .endswith ('shellcheck' ):
79
- return tarf .extractfile (info ).read ()
80
- elif url .endswith ('.zip' ):
81
- with zipfile .ZipFile (bio ) as zipf :
82
- for info in zipf .infolist ():
83
- if info .filename .endswith ('.exe' ):
84
- return zipf .read (info .filename )
85
-
86
- raise AssertionError (f'unreachable { url } ' )
87
-
88
-
89
- def save_executable (data : bytes , base_dir : str ):
90
- exe = 'shellcheck' if sys .platform != 'win32' else 'shellcheck.exe'
91
- output_path = os .path .join (base_dir , exe )
92
- os .makedirs (base_dir , exist_ok = True )
93
-
94
- with open (output_path , 'wb' ) as fp :
95
- fp .write (data )
96
-
97
- # Mark as executable.
98
- # https://stackoverflow.com/a/14105527
99
- mode = os .stat (output_path ).st_mode
100
- mode |= stat .S_IXUSR | stat .S_IXGRP | stat .S_IXOTH
101
- os .chmod (output_path , mode )
102
-
103
-
104
- class build (orig_build ):
105
- sub_commands = orig_build .sub_commands + [('fetch_binaries' , None )]
106
-
107
-
108
- class install (orig_install ):
109
- sub_commands = orig_install .sub_commands + [('install_shellcheck' , None )]
110
-
111
-
112
- class fetch_binaries (Command ):
113
- build_temp = None
114
-
115
- def initialize_options (self ):
116
- pass
117
-
118
- def finalize_options (self ):
119
- self .set_undefined_options ('build' , ('build_temp' , 'build_temp' ))
120
-
121
- def run (self ):
122
- # save binary to self.build_temp
123
- url , sha256 = get_download_url ()
124
- archive = download (url , sha256 )
125
- data = extract (url , archive )
126
- save_executable (data , self .build_temp )
127
-
128
-
129
- class install_shellcheck (Command ):
130
- description = 'install the shellcheck executable'
131
- outfiles = ()
132
- build_dir = install_dir = None
133
-
134
- def initialize_options (self ):
135
- pass
136
-
137
- def finalize_options (self ):
138
- # this initializes attributes based on other commands' attributes
139
- self .set_undefined_options ('build' , ('build_temp' , 'build_dir' ))
140
- self .set_undefined_options (
141
- 'install' , ('install_scripts' , 'install_dir' ),
142
- )
143
-
144
- def run (self ):
145
- self .outfiles = self .copy_tree (self .build_dir , self .install_dir )
146
-
147
- def get_outputs (self ):
148
- return self .outfiles
149
-
150
-
151
- command_overrides = {
152
- 'install' : install ,
153
- 'install_shellcheck' : install_shellcheck ,
154
- 'build' : build ,
155
- 'fetch_binaries' : fetch_binaries ,
156
- }
157
-
158
4
159
5
try :
160
6
from wheel .bdist_wheel import bdist_wheel as orig_bdist_wheel
161
7
except ImportError :
162
- pass
8
+ cmdclass = {}
163
9
else :
164
10
class bdist_wheel (orig_bdist_wheel ):
165
11
def finalize_options (self ):
@@ -172,6 +18,6 @@ def get_tag(self):
172
18
# We don't contain any python source, nor any python extensions
173
19
return 'py2.py3' , 'none' , plat
174
20
175
- command_overrides [ 'bdist_wheel' ] = bdist_wheel
21
+ cmdclass = { 'bdist_wheel' : bdist_wheel }
176
22
177
- setup (version = f' { SHELLCHECK_VERSION } . { PY_VERSION } ' , cmdclass = command_overrides )
23
+ setup (cmdclass = cmdclass )
0 commit comments