1- import hashlib
2- import urllib
31import os
4- import sys
5- import subprocess
6- import shutil
7- import stat
82from collections import namedtuple
9- import fnmatch
103
11- from PIL import Image
12- import requests
134import click
145
15- CHECKSUM_TYPE = "sha256"
16-
17- ICON_SIZES = [ 16 , 32 , 48 , 64 , 96 , 128 , 256 ]
18-
19- TEMP_DIR = "tmp"
20-
21- TOOLS_7ZIP = "./tools/7z"
22- TOOLS_NSIS = "D:/Software/NSIS/Bin/makensis.exe"
6+ from . functions import (
7+ cleanArtifacts ,
8+ cleanBuild ,
9+ copySrc ,
10+ makeIco ,
11+ compileNSISTemplate ,
12+ NSISBuildInstaller ,
13+ )
2314
2415Version = namedtuple ('Version' , ['major' , 'minor' , 'build' ])
2516Version .__str__ = lambda self : "{self.major}.{self.minor}.{self.build}" .format (self = self )
2920def cli ():
3021 pass
3122
32-
33- def download_file (url , target = None , verbose = False ):
34- # https://stackoverflow.com/a/16696317
35- # NOTE the stream=True parameter
36- cs = hashlib .new (CHECKSUM_TYPE )
37- req = requests .get (url , stream = True )
38-
39- url_fn = os .path .basename (urllib .parse .urlparse (req .url ).path )
40-
41- if target is None :
42- outpath = os .path .realpath (url_fn )
43- elif os .path .isdir (target ) or target [- 1 ] in ['/' , '\\ ' ]:
44- outpath = os .path .join (target , url_fn )
45- else :
46- outpath = target
47-
48- os .makedirs (os .path .dirname (outpath ), exist_ok = True )
49-
50- with open (outpath , 'wb' ) as fh :
51- for chunk in req .iter_content (chunk_size = None ):
52- if chunk : # filter out keep-alive new chunks
53- fh .write (chunk )
54- cs .update (chunk )
55- fh .flush ()
56- return (outpath , cs .hexdigest ())
57-
58- def parse_checksum_file (content ):
59- result = {}
60- for line in content .split ('\n ' ):
61- if not line .strip (): continue
62- hex , fn = line .split (maxsplit = 1 )
63- result [fn ] = hex
64- return result
65-
66- def cmd (args , stdout = subprocess .PIPE , stderr = subprocess .DEVNULL , encoding = None ):
67- #print(args)
68- p = subprocess .Popen (args , stdout = stdout , stderr = stderr )
69- if p .stdout is not None :
70- if encoding is not None :
71- p .stdout .read ().decode (encoding )
72- return p .stdout .read ()
73- else :
74- p .wait ()
75- return p .returncode
76-
77- def p7zip_list (fn ):
78- data = cmd ([TOOLS_7ZIP , "l" , "-slt" , "-sccUTF-8" , fn ]).decode ("utf-8" )
79-
80- files = []
81-
82- data = data .replace ('\r \n ' , '\n ' )
83-
84- _crap , archivedata = data .split ('\n --\n ' , 1 )
85- _headers , filedata = archivedata .split ('\n ----------\n ' , 1 )
86- filelist = filedata .split ('\n \n \n ' )[0 ].split ('\n \n ' )
87-
88- for fileinfo in filelist :
89- if not fileinfo .strip (): continue
90-
91- fi = dict ([[y .strip () for y in x .split ('=' , 1 )] for x in fileinfo .strip ().split ('\n ' )])
92-
93- if fi .get ('Attributes' ) == 'D' : continue # Directory
94-
95- files .append ({
96- 'name' : fi .get ('Path' ),
97- 'size' : int (fi .get ('Size' )),
98- 'crc' : fi .get ('CRC' ),
99- })
100-
101- return files
102-
103- def p7zip_open_file (fn , name ):
104- p = subprocess .Popen (
105- [TOOLS_7ZIP , "e" , "-so" , fn ],
106- stdout = subprocess .PIPE ,
107- stderr = subprocess .DEVNULL
108- )
109- return p .stdout
110-
111- def p7zip_extract_file (fn , name , target = None ):
112- with p7zip_open_file (fn , name ) as zfh :
113- if target is None :
114- outpath = os .path .realpath (name )
115- elif os .path .isdir (target ) or target [- 1 ] in ['/' , '\\ ' ]:
116- outpath = os .path .join (target , os .path .basename (name ))
117- else :
118- outpath = target
119-
120- os .makedirs (os .path .dirname (outpath ), exist_ok = True )
121- with open (outpath , "wb" ) as ofh :
122- ofh .write (zfh .read ())
123-
124- return outpath
125-
126- def p7zip_extract (fn , target = None ):
127- if target is None :
128- outpath = "."
129- else :
130- outpath = target
131- os .makedirs (target , exist_ok = True )
132-
133- p = subprocess .Popen (
134- [TOOLS_7ZIP , "x" , "-o{}" .format (outpath ), fn ],
135- stdout = subprocess .DEVNULL ,
136- stderr = subprocess .DEVNULL
137- )
138- p .wait ()
139- return outpath
140-
141-
142-
143- def checksum_file (path , checksum_type = CHECKSUM_TYPE ):
144- cs = hashlib .new (checksum_type )
145- with open (path , "rb" ) as fh :
146- data = fh .read (1_048_576 )
147- while data :
148- cs .update (data )
149- data = fh .read (1_048_576 )
150- return cs .hexdigest ()
151-
152- def cleanBuild (builddir ):
153- print ("Cleaning build directory..." )
154- if os .path .exists (builddir ):
155- for path , _dirs , files in os .walk (builddir ):
156- for name in files :
157- pathname = os .path .join (path , name )
158- # Silly git readonly files.
159- os .chmod (pathname , stat .S_IWRITE )
160- os .unlink (pathname )
161- shutil .rmtree (builddir , onerror = lambda func , path , exec_info : print ("WARNING: Failed to delete " , path , exec_info ))
162-
163- def cleanArtifacts (artifactdir ):
164- print ("Cleaning artifact directory..." )
165- if os .path .exists (artifactdir ):
166- shutil .rmtree (artifactdir , onerror = lambda func , path , exec_info : print ("WARNING: Failed to delete " , path , exec_info ))
167-
168- def copySrc (src_dir , build_dir ):
169- print ("Copying src/*..." )
170- for name in os .listdir (src_dir ):
171- src = os .path .join (src_dir , name )
172- if os .path .isdir (src ):
173- shutil .copytree (src , os .path .join (build_dir , name ))
174- else :
175- shutil .copy (src , build_dir )
176-
177- def makeIco (icon , name , build_dir ):
178- "Generates .ico file from .png"
179- print ("Generating .ico file..." )
180- im = Image .open (icon )
181- fn = "{name}.ico" .format (name )
182- im .save (os .path .join (build_dir , fn ), sizes = [(x ,x ) for x in ICON_SIZES ])
183- return fn
184-
185- def compileNSISTemplate (build_dir , artifact_dir , executables , ** kwargs ):
186- "Generates NSIS script from jinja2 template"
187- print ("Generating NSIS script..." )
188- import jinja2
189- loader = jinja2 .PackageLoader (__package__ )
190- env = jinja2 .Environment (loader = loader , autoescape = False , undefined = jinja2 .StrictUndefined )
191-
192- template = env .get_template ("generic.nsi.j2" )
193-
194- install_files = []
195- install_dirs = []
196- install_size = 0
197- install_executables = []
198- for path , dirs , files in os .walk (build_dir ):
199- for name in files :
200- itempath = os .path .join (path , name )
201- outpath = os .path .relpath (itempath , build_dir )
202-
203- install_files .append (dict (
204- input = itempath ,
205- output = outpath ,
206- ))
207-
208- install_size += os .stat (itempath ).st_size
209-
210- for pat in executables :
211- if fnmatch .fnmatch (outpath , pat ) and outpath not in install_executables :
212- install_executables .append (outpath )
213-
214- for name in dirs :
215- itempath = os .path .join (path , name )
216- relitempath = os .path .relpath (itempath , build_dir )
217- install_dirs .append (relitempath )
218-
219- template_variables = {
220- 'files' : install_files ,
221- 'dirs' : install_dirs ,
222- 'outfile' : os .path .join (artifact_dir , "${APPNAME}-${VERSIONMAJOR}.${VERSIONMINOR}.${VERSIONBUILD}-setup.exe" ),
223- 'size' : install_size ,
224- 'executables' : install_executables ,
225- }
226- template_variables .update (kwargs )
227-
228- nsis_script = os .path .join (build_dir , "generic.nsi" )
229- with open (nsis_script , "w" ) as fh : # TODO: Temp file name.
230- template .stream (** template_variables ).dump (fh )
231- return nsis_script
232-
233- def NSISBuildInstaller (nsi_script , artifact_dir ):
234- print ("Building NSIS installer..." )
235-
236- os .makedirs (artifact_dir , exist_ok = True )
237-
238- # http://nsis.sourceforge.net/Docs/Chapter3.html#usage
239- command = [TOOLS_NSIS , "/NOCD" , "/INPUTCHARSET" , "UTF8" , "/P3" , "/V3" , nsi_script ]
240- print (cmd (command , stdout = sys .stdout , stderr = sys .stderr , encoding = "utf8" ))
241-
24223@cli .command ()
24324@click .option ('--build-dir' , default = "build" , type = click .Path (file_okay = False ))
24425@click .option ('--artifact-dir' , default = "artifacts" , type = click .Path (file_okay = False ))
@@ -308,4 +89,5 @@ def build(ctx, do_clean, version, name, company, description, license, icon, bui
30889 NSISBuildInstaller (nsi_script = nsi_script , artifact_dir = artifact_dir )
30990
31091if __name__ == "__main__" :
311- cli (auto_envvar_prefix = 'SYRUP' )
92+ # pylint:disable=unexpected-keyword-arg
93+ cli (auto_envvar_prefix = 'SYRUP' )
0 commit comments