-
Notifications
You must be signed in to change notification settings - Fork 6
/
BUILDME
executable file
·286 lines (267 loc) · 12.4 KB
/
BUILDME
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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
This is a script to build the project.
It is using template_BUILDME_python3 v5.
This script:
- Should be named "BUILDME"
- Should be present in the project root folder
- Should be executable (chmod +x BUILDME)
- Should contains all the project build process steps implemented into the function BUILDME (see below)
"""
from typing import Sequence
from typing import Optional
def BUILDME(p_Src: str, p_Tmp: str, p_Out: str) -> int:
"""
Build the project.
@param p_Src The project root folder, the build steps shall only access this folder in read only mode
@param p_Tmp A folder where the build steps are free to write temporary file/folder
@param p_Out A folder where the build result should be written to
@returns Exit code 0 on success or raise an Exception if something failed
"""
import os
import platform
import requests
import shutil
# Validate the presence of Required external requirement.
assert os.path.isfile("/bin/sh"), "Requirement not found (/bin/sh)"
assert os.path.isfile("/usr/bin/Xvfb"), "Requirement not found (/usr/bin/Xvfb), run \"sudo apt install xvfb\""
assert shutil.which("mypy") is not None, "Requirement not found (mypy), run \"python3 -m pip install -U mypy\""
#Fetch the latest common atom Continuous Integration Script.
url = "https://raw.githubusercontent.com/atom/ci/master/build-package.sh"
response = requests.get(url, stream=True)
c_BuildpackagePath = os.path.join(p_Tmp, "build-package.sh")
with open(c_BuildpackagePath, "wb") as handle:
for data in response.iter_content():
handle.write(data)
#Copy SRC → TMP
for c_CurrTarget in [p_Tmp]:
for c_CurrFile in ["package.json"]:
shutil.copyfile(os.path.join(p_Src, c_CurrFile), os.path.join(c_CurrTarget, c_CurrFile))
for c_CurrFolder in ["lib", "spec"]:
shutil.copytree(os.path.join(p_Src, c_CurrFolder), os.path.join(c_CurrTarget, c_CurrFolder))
#Prepare required environment variable giving priority to provided values
for currChannel in [os.environ["ATOM_CHANNEL"]] if "ATOM_CHANNEL" in os.environ else ["stable", "beta"]:
env_ATOM_CHANNEL = currChannel
env_TRAVIS_OS_NAME = ""
if "TRAVIS_OS_NAME" in os.environ:
env_TRAVIS_OS_NAME = os.environ["TRAVIS_OS_NAME"]
elif "Linux" == platform.system():
env_TRAVIS_OS_NAME = "linux"
elif "Darwin" == platform.system():
env_TRAVIS_OS_NAME = "osx"
else:
assert False, "Not supported system: " + platform.system()
env_HOME = p_Tmp
import subprocess
c_ProcRun = subprocess.run(
["/bin/sh", c_BuildpackagePath],
check=False,
env={
"PATH": os.environ["PATH"],
"TRAVIS_OS_NAME": env_TRAVIS_OS_NAME,
"ATOM_CHANNEL": env_ATOM_CHANNEL,
"HOME": env_HOME
}
)
c_ProcRun.check_returncode()
ATOM_SCRIPT_NAME="atom" + ("" if currChannel == "stable" else ("-" + currChannel))
c_ProcRun = subprocess.run(
["./node_modules/npm-check-updates/bin/ncu"],
check=False,
env={
"PATH": "/atom/usr/share/"+ ATOM_SCRIPT_NAME + "/resources/app/apm/bin" + os.pathsep + os.environ["PATH"],
"TRAVIS_OS_NAME": env_TRAVIS_OS_NAME,
"ATOM_CHANNEL": env_ATOM_CHANNEL,
"HOME": env_HOME
}
)
#c_ProcRun.check_returncode()
return 0
###Settings
####Validate that the SRC folder is not altered while the build steps are in progress. (Default = True)
#S_SrcHashValidation = False
####Ignore certain folder content when validating if the SRC folder was altered.
######Note: Ignore folders only if excessive build time becomes an issue.
#S_SrcHashValidationIgnoreFolder = []
#S_SrcHashValidationIgnoreFolder.append(".bzr")
#S_SrcHashValidationIgnoreFolder.append(".hg")
#S_SrcHashValidationIgnoreFolder.append(".git")
#S_SrcHashValidationIgnoreFolder.append("CVS")
#S_SrcHashValidationIgnoreFolder.append("SVN")
#S_SrcHashValidationIgnoreFolder.append("node_modules")
########################################################
########################################################
############################
############################ Copy this template build executable to your own project root folder
############################ Anything above this line is considered part of your project
############################ Anything below this line is part of the BUILDME project <https://github.com/elarivie/BUILDME> and is licensed under GPLv3
############################ Note: You don't have to edit anything below this line, if you have to, please fill an issue at https://github.com/elarivie/BUILDME
############################
########################################################
########################################################
def main(p_Args: Sequence[str]) -> int:
"""Main."""
import argparse
parser = argparse.ArgumentParser(
description='Build the project'
, epilog='''
Exit code 0 if build is successfull
'''
)
import os
parser.add_argument('-s', '--source', help='source folder (Default: Current working directory)', action='store', default=os.getcwd())
parser.add_argument('-t', '--temp', help='temporary folder (Default: new temporary directory in the system temporary directory or RAM temp folder if available)', action='store', default=None)
parser.add_argument('-o', '--output', help='output folder, shall not already exist (Default: new temporary directory in TEMPFOLDER)', action='store', default=None)
parser.add_argument('-O', '--Output', help='output folder, shall already exist (Default: --output)', action='store', default=None)
parser.add_argument('-V', '--version', action='version', version='BUILDME 1.1.0')
c_Args = parser.parse_args(p_Args)
def _actualpath(p_Path: Optional[str]) -> Optional[str]:
return None if p_Path is None else os.path.normcase(os.path.realpath(os.path.normpath(p_Path)))
def _processDirectoryHash(p_Path: str) -> str:
"""
Walk the directory tree and process a hash.
@p_Path The directory to process a hash base on its content
@return The Hash (sha512)
"""
if p_Path is None: return ""
import hashlib
c_CheckSum = hashlib.sha512()
for c_Root, c_Dirs, c_Files in os.walk(p_Path, followlinks=False):
for c_Name in c_Files:
c_CheckSum.update(c_Name.encode('utf-8'))#Use the file name
c_CurrPath = os.path.join(c_Root, c_Name)
with open(c_CurrPath, 'rb') as c_CurrentFile:
while True:
v_Buffer = c_CurrentFile.read(8192)
if len(v_Buffer) > 0:
c_CheckSum.update(v_Buffer)#Use the file content
else:
break
for c_Name in c_Dirs:
c_CheckSum.update(c_Name.encode('utf-8'))#Use the directory name
try:
for c_CurrIgnoreFolder in S_SrcHashValidationIgnoreFolder:
if c_CurrIgnoreFolder in c_Dirs:
c_Dirs.remove(c_CurrIgnoreFolder)
except NameError:
pass#Do not Ignore any folder.
return c_CheckSum.hexdigest()
c_ActualPathSrc = _actualpath(c_Args.source)
c_ActualPathTmp = _actualpath(c_Args.temp)
if c_Args.temp is None:
try:
#Use RAM temp folder if available
c_ActualPathTmpRAM = os.path.abspath(os.path.join(os.sep, "run", "user", str(os.getuid())))
if False and os.path.isdir(c_ActualPathTmpRAM):
c_ActualPathTmp = c_ActualPathTmpRAM
except AttributeError:
pass#os.getuid() is only available on Unix (it is the only os which might have a ram temp folder anyway)
c_ActualPathOut = _actualpath(c_Args.output)
v_NeedToCreateOut = True
if c_ActualPathOut is None:
c_ActualPathOut = _actualpath(c_Args.Output)
v_NeedToCreateOut = False
assert (c_ActualPathOut is None) or (os.path.isdir(c_ActualPathOut)), "The output folder has to be a folder"
import tempfile
v_BuildResult = None
c_OrigCWD = os.getcwd()
v_TmpPath = None
v_BuildError = True
try:
with tempfile.TemporaryDirectory(suffix='', prefix='', dir=c_ActualPathTmp) as c_TmpFolder:
v_TmpPath = _actualpath(c_TmpFolder)#This is needed since on the OS Windows which is case insensitive it may create invalid result when comparing path
try:
if c_ActualPathOut is None:
v_NeedToCreateOut = False
c_ActualPathOut = tempfile.mkdtemp(suffix='', prefix='', dir=v_TmpPath)
if c_ActualPathSrc is None:
c_ActualPathSrc = tempfile.mkdtemp(suffix='', prefix='', dir=v_TmpPath)
assert os.path.isdir(c_ActualPathSrc), "The source folder has to be a folder: " + c_ActualPathSrc
assert (c_ActualPathTmp is None) or os.path.isdir(c_ActualPathTmp), "The temporary folder has to be a folder"
assert (c_ActualPathTmp is None) or not c_ActualPathTmp.startswith(c_ActualPathSrc + os.sep), "The temporary folder cannot be within the source folder"
assert not c_ActualPathOut.startswith(c_ActualPathSrc + os.sep), "The output folder cannot be within the source folder"
assert not c_ActualPathSrc.startswith(c_ActualPathOut + os.sep), "The source folder cannot be within the output folder"
assert (c_ActualPathTmp is None) or not c_ActualPathTmp.startswith(c_ActualPathOut + os.sep), "The temporary folder cannot be within the output folder"
if v_NeedToCreateOut:
os.makedirs(c_ActualPathOut, exist_ok=False)
assert os.path.isdir(c_ActualPathOut), "The output folder has to be a folder"
v_DoSrcHashValidation = True
try:
v_DoSrcHashValidation = S_SrcHashValidation
except NameError:
pass
c_SrcHash = _processDirectoryHash(c_ActualPathSrc) if v_DoSrcHashValidation else ""
c_BuildTmpFolder = _actualpath(tempfile.mkdtemp(suffix='', prefix='', dir=v_TmpPath))
os.chdir(c_BuildTmpFolder)#Make sure that the current working directory is the Temp folder before doing the build steps
v_BuildResult = BUILDME(c_ActualPathSrc, c_BuildTmpFolder, c_ActualPathOut)
#From this point, the only thing left to do is to delete the temp folder
v_BuildError = False
finally:
if _actualpath(os.getcwd()).startswith(v_TmpPath + os.sep):
os.chdir(c_OrigCWD)#Get out of the temp folder since it is about to be deleted
except Exception:
if v_BuildError:
raise
else:
pass#Problem cleaning temp folder... will be handled in the finally block
finally:
if v_TmpPath is not None and os.path.isdir(v_TmpPath):
#Failed to remove temp folder
# Known possible cause:
# - On OS Windows if the Tmp folder contains element with long file path
# Will now attempt other strategies to to clean up as much as possible
# Since the temp folder destiny is to be deleted we can alter its content
# Walk multiple time the temp directory:
# - Remove each file and folder specifically
# - If delete is not possible, rename them with a shorter name
# The objective is to:
# - Reduce as much as possible the file path length
# - Keep only the problematic files/folder
try:
v_SawImprovement = True
while v_SawImprovement:
v_SawImprovement = False
for c_Root, c_Dirs, c_Files in os.walk(v_TmpPath, topdown=False):
for c_Name in c_Files:
try:
os.unlink(os.path.join(c_Root, c_Name))
v_SawImprovement = True
except Exception:
try:
os.replace(os.path.join(c_Root, c_Name), os.path.join(c_Root, "A"))#Rename to a single character name
v_SawImprovement = True
except Exception:
pass#Will retry later
for c_Name in c_Dirs:
try:
os.rmdir(os.path.join(c_Root, c_Name))
v_SawImprovement = True
except Exception:
try:
os.replace(os.path.join(c_Root, c_Name), os.path.join(c_Root, "A"))#Rename to a single character name
v_SawImprovement = True
except Exception:
pass#Will retry later
os.rmdir(v_TmpPath)#Give it a try now that the content might have been deleted
except Exception:
pass#This is not a reason to fail the build yet...
if os.path.exists(v_TmpPath):
#Strangely the temp folder is still present...
#No known reason to reach this area but will try other strategies anyway
import shutil
try:
shutil.rmtree(v_TmpPath)#Try a deep delete of the temp folder.
except Exception:
pass#This is not a reason to fail the build yet...
if os.path.exists(v_TmpPath):
import time
time.sleep(5)#In last resort give five seconds to other processes and the OS to breath in case they need to cleanly release their handle on the temp folder
shutil.rmtree(v_TmpPath)#Last chance to delete temp, if it does not work, it will throw an exception to the user... we did our best.
assert not os.path.exists(v_TmpPath), "Temp folder could not be cleaned: " + str(v_TmpPath)
assert c_SrcHash == (_processDirectoryHash(c_ActualPathSrc) if v_DoSrcHashValidation else 0), "Source folder was modified by the build process"
return v_BuildResult
if __name__ == '__main__':
import sys
sys.dont_write_bytecode = True
sys.exit(main(sys.argv[1:]))