Merge pull request #1036 from acemod/makeChanges

Make.py and Make.cfg ehancements
This commit is contained in:
ViperMaul 2015-05-07 06:35:57 -07:00
commit a866b95ce8
2 changed files with 349 additions and 307 deletions

View File

@ -49,10 +49,16 @@ module_root = P:\z\ace\addons
optionals_root = P:\z\ace\optionals optionals_root = P:\z\ace\optionals
# If the command-line variable test, the addons built will be copied to the following folder.
# Default: %USERPROFILE%\documents\Arma 3\<project>
# test_dir = %USERPROFILE%\documents\Arma 3\<project>
# Directory where the built addon will be saved. # Directory where the built addon will be saved.
# Default: release # Default: release
release_dir = P:\z\ace\release release_dir = P:\z\ace\release
# This string will be prefixed to all build PBO file names. # This string will be prefixed to all build PBO file names.
# Default: None # Default: None
pbo_name_prefix = ace_ pbo_name_prefix = ace_

View File

@ -63,6 +63,7 @@ optionals_root = ""
key_name = "ace_3.0.0" key_name = "ace_3.0.0"
key = "" key = ""
dssignfile = "" dssignfile = ""
signature_blacklist = ["ace_server.pbo"]
############################################################################### ###############################################################################
# http://akiscode.com/articles/sha-1directoryhash.shtml # http://akiscode.com/articles/sha-1directoryhash.shtml
@ -280,7 +281,7 @@ def color(color):
def print_error(msg): def print_error(msg):
color("red") color("red")
print ("ERROR: " + msg) print ("ERROR: {}".format(msg))
color("reset") color("reset")
def print_green(msg): def print_green(msg):
@ -311,12 +312,12 @@ def copy_important_files(source_dir,destination_dir):
#copy importantFiles #copy importantFiles
try: try:
print_blue("\nSearching for important files in " + source_dir) print_blue("\nSearching for important files in {}".format(source_dir))
print("Source_dir: " + source_dir) print("Source_dir: {}".format(source_dir))
print("Destination_dir: " + destination_dir) print("Destination_dir: {}".format(destination_dir))
for file in importantFiles: for file in importantFiles:
print_green("Copying file => " + os.path.join(source_dir,file)) print_green("Copying file => {}".format(os.path.join(source_dir,file)))
shutil.copyfile(os.path.join(source_dir,file),os.path.join(destination_dir,file)) shutil.copyfile(os.path.join(source_dir,file),os.path.join(destination_dir,file))
except: except:
print_error("COPYING IMPORTANT FILES.") print_error("COPYING IMPORTANT FILES.")
@ -325,14 +326,14 @@ def copy_important_files(source_dir,destination_dir):
#copy all extension dlls #copy all extension dlls
try: try:
os.chdir(os.path.join(source_dir)) os.chdir(os.path.join(source_dir))
print_blue("\nSearching for DLLs in " + os.getcwd()) print_blue("\nSearching for DLLs in {}".format(os.getcwd()))
filenames = glob.glob("*.dll") filenames = glob.glob("*.dll")
if not filenames: if not filenames:
print ("Empty SET") print ("Empty SET")
for dll in filenames: for dll in filenames:
print_green("Copying dll => " + os.path.join(source_dir,dll)) print_green("Copying dll => {}".format(os.path.join(source_dir,dll)))
if os.path.isfile(dll): if os.path.isfile(dll):
shutil.copyfile(os.path.join(source_dir,dll),os.path.join(destination_dir,dll)) shutil.copyfile(os.path.join(source_dir,dll),os.path.join(destination_dir,dll))
except: except:
@ -353,17 +354,17 @@ def copy_optionals_for_building(mod,pbos):
files = glob.glob(os.path.join(release_dir, "@ace","optionals","*.pbo")) files = glob.glob(os.path.join(release_dir, "@ace","optionals","*.pbo"))
for file in files: for file in files:
file_name = os.path.basename(file) file_name = os.path.basename(file)
#print ("Adding the following file: " + file_name) #print ("Adding the following file: {}".format(file_name))
pbos.append(file_name) pbos.append(file_name)
pbo_path = os.path.join(release_dir, "@ace","optionals",file_name) pbo_path = os.path.join(release_dir, "@ace","optionals",file_name)
sigFile_name = file_name +"."+ key_name + ".bisign" sigFile_name = file_name +"."+ key_name + ".bisign"
sig_path = os.path.join(release_dir, "@ace","optionals",sigFile_name) sig_path = os.path.join(release_dir, "@ace","optionals",sigFile_name)
if (os.path.isfile(pbo_path)): if (os.path.isfile(pbo_path)):
print("Moving " + pbo_path + " for processing.") print("Moving {} for processing.".format(pbo_path))
shutil.move(pbo_path, os.path.join(release_dir,"@ace","addons",file_name)) shutil.move(pbo_path, os.path.join(release_dir,"@ace","addons",file_name))
if (os.path.isfile(sig_path)): if (os.path.isfile(sig_path)):
#print("Moving " + sig_path + " for processing.") #print("Moving {} for processing.".format(sig_path))
shutil.move(sig_path, os.path.join(release_dir,"@ace","addons",sigFile_name)) shutil.move(sig_path, os.path.join(release_dir,"@ace","addons",sigFile_name))
except: except:
print_error("Error in moving") print_error("Error in moving")
@ -385,7 +386,7 @@ def copy_optionals_for_building(mod,pbos):
else: else:
destination = os.path.join(module_root,dir_name) destination = os.path.join(module_root,dir_name)
print("Temporarily copying " + os.path.join(optionals_root,dir_name) + " => " + destination + " for building.") print("Temporarily copying {} => {} for building.".format(os.path.join(optionals_root,dir_name),destination))
if (os.path.exists(destination)): if (os.path.exists(destination)):
shutil.rmtree(destination, True) shutil.rmtree(destination, True)
shutil.copytree(os.path.join(optionals_root,dir_name), destination) shutil.copytree(os.path.join(optionals_root,dir_name), destination)
@ -406,7 +407,7 @@ def cleanup_optionals(mod):
else: else:
destination = os.path.join(module_root,dir_name) destination = os.path.join(module_root,dir_name)
print("Cleaning " + destination) print("Cleaning {}".format(destination))
try: try:
file_name = "ace_{}.pbo".format(dir_name) file_name = "ace_{}.pbo".format(dir_name)
@ -418,10 +419,10 @@ def cleanup_optionals(mod):
dst_sig_path = os.path.join(release_dir, "@ace","optionals",sigFile_name) dst_sig_path = os.path.join(release_dir, "@ace","optionals",sigFile_name)
if (os.path.isfile(src_file_path)): if (os.path.isfile(src_file_path)):
#print("Preserving " + file_name) #print("Preserving {}".format(file_name))
os.renames(src_file_path,dst_file_path) os.renames(src_file_path,dst_file_path)
if (os.path.isfile(src_sig_path)): if (os.path.isfile(src_sig_path)):
#print("Preserving " + sigFile_name) #print("Preserving {}".format(sigFile_name))
os.renames(src_sig_path,dst_sig_path) os.renames(src_sig_path,dst_sig_path)
except FileExistsError: except FileExistsError:
print_error(file_name + " already exists") print_error(file_name + " already exists")
@ -434,7 +435,7 @@ def cleanup_optionals(mod):
def purge(dir, pattern, friendlyPattern="files"): def purge(dir, pattern, friendlyPattern="files"):
print_green("Deleting " + friendlyPattern + " files from directory: " + dir) print_green("Deleting {} files from directory: {}".format(friendlyPattern,dir))
for f in os.listdir(dir): for f in os.listdir(dir):
if re.search(pattern, f): if re.search(pattern, f):
os.remove(os.path.join(dir, f)) os.remove(os.path.join(dir, f))
@ -443,7 +444,12 @@ def purge(dir, pattern, friendlyPattern="files"):
def build_signature_file(file_name): def build_signature_file(file_name):
global key global key
global dssignfile global dssignfile
print("Signing with " + key + ".") global signature_blacklist
ret = 0
baseFile = os.path.basename(file_name)
#print_yellow("Sig_fileName: {}".format(baseFile))
if not (baseFile in signature_blacklist):
print("Signing with {}.".format(key))
ret = subprocess.call([dssignfile, key, file_name]) ret = subprocess.call([dssignfile, key, file_name])
if ret == 0: if ret == 0:
return True return True
@ -456,12 +462,30 @@ def check_for_obsolete_pbos(addonspath, file):
if not os.path.exists(os.path.join(addonspath, module)): if not os.path.exists(os.path.join(addonspath, module)):
return True return True
return False return False
def config_restore(modulePath):
#PABST: cleanup config BS (you could comment this out to see the "de-macroed" cpp
#print_green("\Pabst! (restoring): {}".format(os.path.join(modulePath, "config.cpp")))
try:
if os.path.isfile(os.path.join(modulePath, "config.cpp")):
os.remove(os.path.join(modulePath, "config.cpp"))
if os.path.isfile(os.path.join(modulePath, "config.backup")):
os.rename(os.path.join(modulePath, "config.backup"), os.path.join(modulePath, "config.cpp"))
if os.path.isfile(os.path.join(modulePath, "config.bin")):
os.remove(os.path.join(modulePath, "config.bin"))
if os.path.isfile(os.path.join(modulePath, "texHeaders.bin")):
os.remove(os.path.join(modulePath, "texHeaders.bin"))
except:
print_yellow("Some error occurred. Check your addon folder {} for integrity".format(modulePath))
return True
############################################################################### ###############################################################################
def main(argv): def main(argv):
"""Build an Arma addon suite in a directory from rules in a make.cfg file.""" """Build an Arma addon suite in a directory from rules in a make.cfg file."""
print_blue(("\nmake.py for Arma, modified for Advanced Combat Environment v" + __version__)) print_blue("\nmake.py for Arma, modified for Advanced Combat Environment v{}".format(__version__))
global work_drive global work_drive
global module_root global module_root
@ -567,7 +591,7 @@ See the make.cfg file for additional build options.
else: else:
check_external = False check_external = False
print_yellow("\nCheck external references is set to " + str(check_external)) print_yellow("\nCheck external references is set to {}".format(str(check_external)))
# Get the directory the make script is in. # Get the directory the make script is in.
make_root = os.path.dirname(os.path.realpath(__file__)) make_root = os.path.dirname(os.path.realpath(__file__))
@ -625,6 +649,9 @@ See the make.cfg file for additional build options.
# Release/build directory, relative to script dir # Release/build directory, relative to script dir
release_dir = cfg.get(make_target, "release_dir", fallback="release") release_dir = cfg.get(make_target, "release_dir", fallback="release")
#Directory to copy the final built PBO's for a test run.
test_dir = cfg.get(make_target, "test_dir", fallback=os.path.join(os.environ["USERPROFILE"],r"documents\Arma 3"))
# Project PBO file prefix (files are renamed to prefix_name.pbo) # Project PBO file prefix (files are renamed to prefix_name.pbo)
pbo_name_prefix = cfg.get(make_target, "pbo_name_prefix", fallback=None) pbo_name_prefix = cfg.get(make_target, "pbo_name_prefix", fallback=None)
@ -632,21 +659,21 @@ See the make.cfg file for additional build options.
module_root_parent = os.path.abspath(os.path.join(os.path.join(work_drive, prefix), os.pardir)) module_root_parent = os.path.abspath(os.path.join(os.path.join(work_drive, prefix), os.pardir))
module_root = cfg.get(make_target, "module_root", fallback=os.path.join(make_root_parent, "addons")) module_root = cfg.get(make_target, "module_root", fallback=os.path.join(make_root_parent, "addons"))
optionals_root = os.path.join(module_root_parent, "optionals") optionals_root = os.path.join(module_root_parent, "optionals")
print_green ("module_root: " + module_root) print_green ("module_root: {}".format(module_root))
if (os.path.isdir(module_root)): if (os.path.isdir(module_root)):
os.chdir(module_root) os.chdir(module_root)
else: else:
print_error ("Directory " + module_root + " does not exist.") print_error ("Directory {} does not exist.".format(module_root))
sys.exit() sys.exit()
if (os.path.isdir(optionals_root)): if (os.path.isdir(optionals_root)):
print_green ("optionals_root: " + optionals_root) print_green ("optionals_root: {}".format(optionals_root))
else: else:
print_error ("Directory " + optionals_root + " does not exist.") print_error ("Directory {} does not exist.".format(optionals_root))
sys.exit() sys.exit()
print_green ("release_dir: " + release_dir) print_green ("release_dir: {}".format(release_dir))
except: except:
raise raise
@ -709,6 +736,8 @@ See the make.cfg file for additional build options.
print_error("Cannot create release directory") print_error("Cannot create release directory")
raise raise
try:
#Temporarily copy optionals_root for building. They will be removed later. #Temporarily copy optionals_root for building. They will be removed later.
optionals_modules = [] optionals_modules = []
optional_files = [] optional_files = []
@ -739,7 +768,7 @@ See the make.cfg file for additional build options.
ret = subprocess.call([dscreatekey, key_name]) # Created in make_root ret = subprocess.call([dscreatekey, key_name]) # Created in make_root
os.chdir(curDir) os.chdir(curDir)
if ret == 0: if ret == 0:
print_green("Created: " + os.path.join(private_key_path, key_name + ".biprivatekey")) print_green("Created: {}".format(os.path.join(private_key_path, key_name + ".biprivatekey")))
print("Removing any old signature keys...") print("Removing any old signature keys...")
purge(os.path.join(module_root, release_dir, project, "addons"), "^.*\.bisign$","*.bisign") purge(os.path.join(module_root, release_dir, project, "addons"), "^.*\.bisign$","*.bisign")
purge(os.path.join(module_root, release_dir, project, "optionals"), "^.*\.bisign$","*.bisign") purge(os.path.join(module_root, release_dir, project, "optionals"), "^.*\.bisign$","*.bisign")
@ -762,7 +791,7 @@ See the make.cfg file for additional build options.
raise raise
else: else:
print_green("\nNOTE: Using key " + os.path.join(private_key_path, key_name + ".biprivatekey")) print_green("\nNOTE: Using key {}".format(os.path.join(private_key_path, key_name + ".biprivatekey")))
key = os.path.join(private_key_path, key_name + ".biprivatekey") key = os.path.join(private_key_path, key_name + ".biprivatekey")
@ -773,13 +802,13 @@ See the make.cfg file for additional build options.
if (file.endswith(".pbo") and os.path.isfile(os.path.join(obsolete_check_path,file))): if (file.endswith(".pbo") and os.path.isfile(os.path.join(obsolete_check_path,file))):
if check_for_obsolete_pbos(module_root, file): if check_for_obsolete_pbos(module_root, file):
fileName = os.path.splitext(file)[0] fileName = os.path.splitext(file)[0]
print_yellow("Removing obsolete file => " + file) print_yellow("Removing obsolete file => {}".format(file))
purge(obsolete_check_path,fileName+"\..",fileName+".*") purge(obsolete_check_path,fileName+"\..",fileName+".*")
# For each module, prep files and then build. # For each module, prep files and then build.
print_blue("\nBuilding...") print_blue("\nBuilding...")
for module in modules: for module in modules:
print_green("\nMaking " + module + "-"*max(1, (60-len(module)))) print_green("\nMaking {}".format(module + "-"*max(1, (60-len(module)))))
missing = False missing = False
sigMissing = False sigMissing = False
@ -811,7 +840,7 @@ See the make.cfg file for additional build options.
print("Module has not changed.") print("Module has not changed.")
if sigMissing: if sigMissing:
if key: if key:
print("Missing Signature key " + sigFile) print("Missing Signature key {}".format(sigFile))
build_signature_file(os.path.join(module_root, release_dir, project, "addons", pbo_name_prefix + module + ".pbo")) build_signature_file(os.path.join(module_root, release_dir, project, "addons", pbo_name_prefix + module + ".pbo"))
# Skip everything else # Skip everything else
continue continue
@ -833,7 +862,7 @@ See the make.cfg file for additional build options.
print("Resuming build...") print("Resuming build...")
continue continue
#else: #else:
#print("WARNING: Module is stored on work drive (" + work_drive + ").") #print("WARNING: Module is stored on work drive ({}).".format(work_drive))
try: try:
# Remove the old pbo, key, and log # Remove the old pbo, key, and log
@ -855,8 +884,8 @@ See the make.cfg file for additional build options.
continue continue
# Build the module into a pbo # Build the module into a pbo
print_blue("Building: " + os.path.join(work_drive, prefix, module)) print_blue("Building: {}".format(os.path.join(work_drive, prefix, module)))
print_blue("Destination: " + os.path.join(module_root, release_dir, project, "addons")) print_blue("Destination: {}".format(os.path.join(module_root, release_dir, project, "addons")))
# Make destination folder (if needed) # Make destination folder (if needed)
try: try:
@ -864,6 +893,7 @@ See the make.cfg file for additional build options.
except: except:
pass pass
# Run build tool # Run build tool
build_successful = False build_successful = False
if build_tool == "pboproject": if build_tool == "pboproject":
@ -876,14 +906,14 @@ See the make.cfg file for additional build options.
cmd = [os.path.join(arma3tools_path, "CfgConvert", "CfgConvert.exe"), "-bin", "-dst", os.path.join(work_drive, prefix, module, "config.bin"), os.path.join(work_drive, prefix, module, "config.cpp")] cmd = [os.path.join(arma3tools_path, "CfgConvert", "CfgConvert.exe"), "-bin", "-dst", os.path.join(work_drive, prefix, module, "config.bin"), os.path.join(work_drive, prefix, module, "config.cpp")]
ret = subprocess.call(cmd) ret = subprocess.call(cmd)
if ret != 0: if ret != 0:
print_error("CfgConvert -bin return code == " + str(ret) + ". Usually means there is a syntax error within the config.cpp file.") print_error("CfgConvert -bin return code == {}. Usually means there is a syntax error within the config.cpp file.".format(str(ret)))
os.remove(os.path.join(work_drive, prefix, module, "config.cpp")) os.remove(os.path.join(work_drive, prefix, module, "config.cpp"))
shutil.copyfile(os.path.join(work_drive, prefix, module, "config.backup"), os.path.join(work_drive, prefix, module, "config.cpp")) shutil.copyfile(os.path.join(work_drive, prefix, module, "config.backup"), os.path.join(work_drive, prefix, module, "config.cpp"))
cmd = [os.path.join(arma3tools_path, "CfgConvert", "CfgConvert.exe"), "-txt", "-dst", os.path.join(work_drive, prefix, module, "config.cpp"), os.path.join(work_drive, prefix, module, "config.bin")] cmd = [os.path.join(arma3tools_path, "CfgConvert", "CfgConvert.exe"), "-txt", "-dst", os.path.join(work_drive, prefix, module, "config.cpp"), os.path.join(work_drive, prefix, module, "config.bin")]
ret = subprocess.call(cmd) ret = subprocess.call(cmd)
if ret != 0: if ret != 0:
print_error("CfgConvert -txt return code == " + str(ret) + ". Usually means there is a syntax error within the config.cpp file.") print_error("CfgConvert -txt return code == {}. Usually means there is a syntax error within the config.cpp file.".format(str(ret)))
os.remove(os.path.join(work_drive, prefix, module, "config.cpp")) os.remove(os.path.join(work_drive, prefix, module, "config.cpp"))
shutil.copyfile(os.path.join(work_drive, prefix, module, "config.backup"), os.path.join(work_drive, prefix, module, "config.cpp")) shutil.copyfile(os.path.join(work_drive, prefix, module, "config.backup"), os.path.join(work_drive, prefix, module, "config.cpp"))
@ -930,7 +960,7 @@ See the make.cfg file for additional build options.
color("reset") color("reset")
if ret == 0: if ret == 0:
print_green("pboProject return code == " + str(ret)) print_green("pboProject return code == {}".format(str(ret)))
# Prettyprefix rename the PBO if requested. # Prettyprefix rename the PBO if requested.
if pbo_name_prefix: if pbo_name_prefix:
try: try:
@ -940,7 +970,7 @@ See the make.cfg file for additional build options.
print_error("Could not rename built PBO with prefix.") print_error("Could not rename built PBO with prefix.")
# Sign result # Sign result
if key: if key:
print("Signing with " + key + ".") print("Signing with {}.".format(key))
if pbo_name_prefix: if pbo_name_prefix:
ret = subprocess.call([dssignfile, key, os.path.join(module_root, release_dir, project, "addons", pbo_name_prefix + module + ".pbo")]) ret = subprocess.call([dssignfile, key, os.path.join(module_root, release_dir, project, "addons", pbo_name_prefix + module + ".pbo")])
else: else:
@ -952,17 +982,11 @@ See the make.cfg file for additional build options.
build_successful = True build_successful = True
if not build_successful: if not build_successful:
print_error("pboProject return code == " + str(ret)) print_error("pboProject return code == {}".format(str(ret)))
print_error("Module not successfully built/signed.") print_error("Module not successfully built/signed.")
print ("Resuming build...") print ("Resuming build...")
continue continue
#PABST: cleanup config BS (you could comment this out to see the "de-macroed" cpp
#print_green("\Pabst (restoring): " + os.path.join(work_drive, prefix, module, "config.cpp"))
os.remove(os.path.join(work_drive, prefix, module, "config.cpp"))
os.remove(os.path.join(work_drive, prefix, module, "config.bin"))
os.rename(os.path.join(work_drive, prefix, module, "config.backup"), os.path.join(work_drive, prefix, module, "config.cpp"))
# Back to the root # Back to the root
os.chdir(module_root) os.chdir(module_root)
@ -972,6 +996,8 @@ See the make.cfg file for additional build options.
input("Press Enter to continue...") input("Press Enter to continue...")
print ("Resuming build...") print ("Resuming build...")
continue continue
finally:
config_restore(os.path.join(work_drive, prefix, module))
elif build_tool== "addonbuilder": elif build_tool== "addonbuilder":
# Detect $NOBIN$ and do not binarize if found. # Detect $NOBIN$ and do not binarize if found.
@ -998,10 +1024,10 @@ See the make.cfg file for additional build options.
else: else:
previousDirectory = os.getcwd() previousDirectory = os.getcwd()
os.chdir(arma3tools_path) os.chdir(arma3tools_path)
print_error("Current directory - " + os.getcwd()) print_error("Current directory - {}".format(os.getcwd()))
ret = subprocess.call(cmd) ret = subprocess.call(cmd)
os.chdir(previousDirectory) os.chdir(previousDirectory)
print_error("Current directory - " + os.getcwd()) print_error("Current directory - {}".format(os.getcwd()))
color("reset") color("reset")
print_green("completed") print_green("completed")
# Prettyprefix rename the PBO if requested. # Prettyprefix rename the PBO if requested.
@ -1014,8 +1040,10 @@ See the make.cfg file for additional build options.
if ret == 0: if ret == 0:
# Sign result # Sign result
if key:
print("Signing with " + key + ".") #print_yellow("Sig_fileName: ace_{}.pbo".format(module))
if (key and not "ace_{}.pbo".format(module) in signature_blacklist) :
print("Signing with {}.".format(key))
if pbo_name_prefix: if pbo_name_prefix:
ret = subprocess.call([dssignfile, key, os.path.join(make_root, release_dir, project, "addons", pbo_name_prefix + module + ".pbo")]) ret = subprocess.call([dssignfile, key, os.path.join(make_root, release_dir, project, "addons", pbo_name_prefix + module + ".pbo")])
else: else:
@ -1040,12 +1068,18 @@ See the make.cfg file for additional build options.
continue continue
else: else:
print_error("Unknown build_tool " + build_tool + "!") print_error("Unknown build_tool {}!".format(build_tool))
# Update the hash for a successfully built module # Update the hash for a successfully built module
if build_successful: if build_successful:
cache[module] = new_sha cache[module] = new_sha
except:
print_yellow("Cancel or some error detected.")
finally:
copy_important_files(module_root_parent,os.path.join(release_dir, "@ace"))
cleanup_optionals(optionals_modules)
# Done building all modules! # Done building all modules!
# Write out the cache state # Write out the cache state
@ -1060,12 +1094,9 @@ See the make.cfg file for additional build options.
except: except:
print_error("ERROR: Could not delete pboProject temp files.") print_error("ERROR: Could not delete pboProject temp files.")
copy_important_files(module_root_parent,os.path.join(release_dir, "@ace"))
cleanup_optionals(optionals_modules)
# Make release # Make release
if make_release: if make_release:
print_blue("\nMaking release: " + project + "-" + release_version + ".zip") print_blue("\nMaking release: {}-{}.zip".format(project,release_version))
try: try:
# Delete all log files # Delete all log files
@ -1095,6 +1126,11 @@ See the make.cfg file for additional build options.
else: else:
a3_path = cygwin_a3path a3_path = cygwin_a3path
print_yellow("Path from the registry => {}".format(a3_path))
a3_path = test_dir
print_yellow("Copying build files to {}".format(a3_path))
if os.path.exists(a3_path): if os.path.exists(a3_path):
try: try:
shutil.rmtree(os.path.join(a3_path, project), True) shutil.rmtree(os.path.join(a3_path, project), True)