diff --git a/addons/main/script_mod.hpp b/addons/main/script_mod.hpp index 5e3c717d89..78fcc0c665 100644 --- a/addons/main/script_mod.hpp +++ b/addons/main/script_mod.hpp @@ -3,10 +3,7 @@ #define MAINPREFIX z #define PREFIX ace -#define MAJOR 3 -#define MINOR 8 -#define PATCHLVL 0 -#define BUILD 10 +#include "script_version.hpp" #define VERSION MAJOR.MINOR.PATCHLVL.BUILD #define VERSION_AR MAJOR,MINOR,PATCHLVL,BUILD diff --git a/addons/main/script_version.hpp b/addons/main/script_version.hpp new file mode 100644 index 0000000000..edc3cf0bbb --- /dev/null +++ b/addons/main/script_version.hpp @@ -0,0 +1,4 @@ +#define MAJOR 3 +#define MINOR 8 +#define PATCHLVL 0 +#define BUILD 10 diff --git a/docs/wiki/development/setting-up-the-development-environment.md b/docs/wiki/development/setting-up-the-development-environment.md index 10c515a76d..c0333ac590 100644 --- a/docs/wiki/development/setting-up-the-development-environment.md +++ b/docs/wiki/development/setting-up-the-development-environment.md @@ -16,9 +16,10 @@ This page describes how you can setup your development environment for ACE3, all - A proper installation of the Arma 3 Tools (available on Steam) - A properly setup P-drive - Run Arma 3 and Arma 3 Tools directly from steam once to install registry entries (and again after every update) -- Python 3.x, available [here](http://www.python.org) -- The following Mikero's Tools (available [here](https://dev.withsix.com/projects/mikero-pbodll/files)): DePbo, DeRap, DeOgg, Rapify, MakePbo, pboProject -- A properly setup PATH variable (containing Python, the Mikero's Tools and git) +- [Python 3.x](https://www.python.org/) +- [Mikero Tools](https://dev.withsix.com/projects/mikero-pbodll/files): DePbo, DeRap, DeOgg, Rapify, MakePbo, PboProject +- Python, Mikero Tools and Git in PATH environment variable +- [CBA](https://github.com/CBATeam/CBA_A3/releases/latest) mod (release or development version) ## 2. Why so complicated? @@ -28,24 +29,29 @@ If you have contributed to AGM you might be used to an easier build process, whe Not offering .exes for the Python scripts we use allows us to make easy changes without the hassle of compiling self-extracting exes all the time. -## 3. Getting ACE +## 3. Getting Source Code -To actually get the ACE source code on your machine, it is recommended that you use Git. Tutorials for this are all around the web, and it allows you to track your changes and easily update your local copy. +To actually get the ACE3 source code on your machine, it is recommended that you use Git. Tutorials for this are all around the web, and it allows you to track your changes and easily update your local copy. If you just want to create a quick and dirty build, you can also directly download the source code using the "Download ZIP" button on the front page of the GitHub repo. -## 4. Initial Setup +## 4. Setup and Building -After ensuring that you have installed all requirements, execute the `setup.py` script found in the `tools` folder. This will do most of the heavy lifting for you, create the links you need and copy the required CBA code to the proper place. Please note that these links are tied to the location of your ACE3 source code, so make sure that the project folder is where you want it to be. We recommend that you store the ACE3 project on your P-drive. +### 4.1 Initial Setup -### 4.1 Manual Setup +Navigate to `tools` folder in command line. -Should the script fail, here is how you create the required links manually: +``` +cd "[location of the ACE3 project]\tools" +``` -First, to set up the links, create `z` folders both in your Arma 3 directory and on your P-drive. Then run the following commands as admin, replacing the text in brackets with the appropriate paths: +Execute `setup.py` to create symbolic links to P-drive and Arma 3 directory required for building. -```sh + +Should the script fail, you can create the required links manually. First, create `z` folders both in your Arma 3 directory and on your P-drive. Then run the following commands as admin, replacing the text in brackets with the appropriate paths: + +```bat mklink /J "[Arma 3 installation folder]\z\ace" "[location of the ACE3 project]" mklink /J "P:\z\ace" "[location of the ACE3 project]" ``` @@ -53,33 +59,50 @@ mklink /J "P:\z\ace" "[location of the ACE3 project]" Then, copy the `cba` folder from the `tools` folder to `P:\x\cba`. Create the `x` folder if needed. That folder contains the parts of the CBA source code that are required for the macros to work. -## 5. Creating a Test Build +## 4.2 Creating a Test Build -To create a development build of ACE to test changes or to debug something, run the `build.py` file in the `tools` folder. This will populate the `addons` folder with binarized PBOs. These PBOs still point to the source files in their respective folders however, which allows you to use [file patching](#file-patching). - -This also means that you cannot distribute this build to others. +To create a development build of ACE3 to test changes or to debug something, run the `build.py` file in the `tools` folder. This will populate the `addons` folder with binarized PBOs. These PBOs still point to the source files in their respective folders however, which allows you to use [file patching](#file-patching). This also means that you cannot distribute this build to others. To start the game using this build, you can use the following modline: ```sh --mod=@cba_a3;z\ace +-mod=@CBA_A3;z\ace ``` +## 4.3 Creating a Release Build -## 6. Creating a Release Build +To create a complete build of ACE3 that you can use without the source files you will need to: +- Ensure `.hpp` is **NOT** in pboProject's "Exclude From Pbo" list -To create a complete build of ACE that you can use without the source files, run the `make.py` file in the `tools` folder. This will populate the `release` folder with binarized PBOs that you can redistribute. These handle like those of any other mod. +When the requirements are met: +- Execute `make.py version increment_build force check_external release` in the `tools` folder, replacing `` with the part of version you want to increment (options described below) + +This will populate the `release` folder with binarized PBOs, compiled extensions, copied extras, bisigns and a bikey. Additionally, an archive file will also be created in the folder. The folder and archive handle like those of any other mod. + +Different `make.py` command line options include: +- `version` - update version number in all files and leave them in working directory (leaving this out will still update the version in all files present in the `release` folder, but they will be reverted to not disturb the working directory) +- `increment_build` - increments _build_ version number +- `increment_patch` - increments _patch_ version number (ignored with `increment_minor` or `increment_major`) +- `increment_minor` - increments _minor_ version number and resets _patch_ version number to `0` (ignored with `increment_major`) +- `increment_major` - increments _major_ version number and resets _minor_ and _patch_ version numbers to `0` +- `force` - force rebuild all PBOs, even those already present in the `release` directory (combined with `compile` it will also rebuild all extensions) +- `check_external` - check external references (incompatible only with ` ` and `force `) +- `release` - create release packages/archives +- ` ` - build only specified component(s) (incompatible with `release`) +- `force ` - force rebuild specified component(s) (incompatible with `release`) ## 7. File Patching -File Patching allows you to change the files in an addon while the game is running, requiring only a restart of the mission. This makes it great for debugging, as it cuts down the time required between tests. Note that this only works with PBOs created using MakePBO, as outlined in [Creating a Test Build](#creating-a-test-build). +File Patching allows you to change the files in an addon while the game is running, requiring only a restart of the mission. This makes it great for debugging, as it cuts down the time required between tests. Note that this only works with PBOs created using MakePBO, which `build.py` uses. + +To run Arma 3 with file patching add the `-filePatching` startup parameter (since Arma 3 v1.50, file patching is disabled by default). ### 7.1 Disabling CBA Function Caching By default CBA caches a compiled version of functions to reduce mission load times. This interferes with file patching. There are three ways to disable function caching: -- Load `cba_cache_disable.pbo` (included in CBA's optional folder) +- Load `cba_cache_disable.pbo` (included in CBA's optional folder - simply move it to `addons` folder for the time being) - Add the following to your test missions description.ext: ```cpp @@ -94,20 +117,16 @@ class CfgSettings { }; ``` -- To only disable caching for a single module, hence greatly improving mission restart time, add the following line to the `script_component.hpp` file of said module: +- To only disable caching for a single module, hence greatly improving mission restart time, add the following line to the `script_component.hpp` file of said module (prepared in each ACE3 component, simply uncomment): ```cpp #define DISABLE_COMPILE_CACHE ``` -### 7.2 Running Arma 3 with File Patching Enabled - -Starting from version 1.50, Arma 3 was changed to disable file patching by default, making the feature opt-in. To execute the game with file patching enabled add the `-filePatching` flag to the command line. - -### 7.3 Restrictions of File Patching +### 7.2 Restrictions Files must exist in the built PBOs for file patching to work. If you create a new file you must rebuild the PBO or Arma will not find it in your file paths. Configs are not patched during run time, only at load time. You do not have have to rebuild a PBO to make config changes, just restart Arma. You can get around this though if you are on the dev branch of Arma 3 and running the [diagnostic exe](https://community.bistudio.com/wiki/Arma_3_Diagnostics_Exe). That includes `diag_mergeConfigFile` which takes a full system path (as in `diag_mergeConfigFile ["p:\z\ace\addons\my_module\config.cpp"]`) and allows you selectively reload config files. -If you need to add/remove files, then you'll need to run build.py again without the game running, and restart. That is all that is required to add new files to then further use in testing. +If you need to add/remove files, then you'll need to run `build.py` again without the game running, and restart. That is all that is required to add new files for further use in testing. diff --git a/tools/make.py b/tools/make.py index 530eeaf032..e7d5adbd68 100644 --- a/tools/make.py +++ b/tools/make.py @@ -30,7 +30,7 @@ ############################################################################### -__version__ = "0.7" +__version__ = "0.8" import sys @@ -51,8 +51,7 @@ import traceback import time import timeit import re - -from tempfile import mkstemp +import fileinput if sys.platform == "win32": import winreg @@ -329,7 +328,6 @@ def print_yellow(msg): def copy_important_files(source_dir,destination_dir): - originalDir = os.getcwd() # Copy importantFiles @@ -340,17 +338,17 @@ def copy_important_files(source_dir,destination_dir): for file in importantFiles: filePath = os.path.join(module_root_parent, file) - # Take only file name for destination path (to put it into root of release dir) - if "\\" in file: - count = file.count("\\") - file = file.split("\\", count)[-1] - print_green("Copying file => {}".format(os.path.join(source_dir,file))) - shutil.copyfile(os.path.join(source_dir,filePath),os.path.join(destination_dir,file)) + if os.path.exists(filePath): + print_green("Copying file => {}".format(filePath)) + shutil.copy(os.path.join(source_dir,filePath), destination_dir) + else: + missingFiles.append("{}".format(filePath)) + print_error("Failed copying file => {}".format(filePath)) except: print_error("COPYING IMPORTANT FILES.") raise - #copy all extension dlls + # Copy all extension DLL's try: os.chdir(os.path.join(source_dir)) print_blue("\nSearching for DLLs in {}".format(os.getcwd())) @@ -370,13 +368,13 @@ def copy_important_files(source_dir,destination_dir): os.chdir(originalDir) + def copy_optionals_for_building(mod,pbos): src_directories = os.listdir(optionals_root) current_dir = os.getcwd() - print_blue("\nChecking Optionals folder...") + print_blue("\nChecking optionals folder...") try: - #special server.pbo processing files = glob.glob(os.path.join(release_dir, project, "optionals", "*.pbo")) for file in files: @@ -400,7 +398,6 @@ def copy_optionals_for_building(mod,pbos): finally: os.chdir(current_dir) - print("") try: for dir_name in src_directories: mod.append(dir_name) @@ -467,9 +464,10 @@ def cleanup_optionals(mod): def purge(dir, pattern, friendlyPattern="files"): print_green("Deleting {} files from directory: {}".format(friendlyPattern,dir)) - for f in os.listdir(dir): - if re.search(pattern, f): - os.remove(os.path.join(dir, f)) + if os.path.exists(dir): + for f in os.listdir(dir): + if re.search(pattern, f): + os.remove(os.path.join(dir, f)) def build_signature_file(file_name): @@ -525,13 +523,13 @@ def addon_restore(modulePath): return True -def get_project_version(): +def get_project_version(version_increments=[]): global project_version versionStamp = project_version #do the magic based on https://github.com/acemod/ACE3/issues/806#issuecomment-95639048 try: - scriptModPath = os.path.join(work_drive, prefix, "main\script_mod.hpp") + scriptModPath = os.path.join(module_root, "main\script_version.hpp") if os.path.isfile(scriptModPath): f = open(scriptModPath, "r") @@ -541,11 +539,36 @@ def get_project_version(): if hpptext: majorText = re.search(r"#define MAJOR (.*\b)", hpptext).group(1) minorText = re.search(r"#define MINOR (.*\b)", hpptext).group(1) - patchlvlText = re.search(r"#define PATCHLVL (.*\b)", hpptext).group(1) + patchText = re.search(r"#define PATCHLVL (.*\b)", hpptext).group(1) buildText = re.search(r"#define BUILD (.*\b)", hpptext).group(1) + # Increment version (reset all below except build) + if version_increments != []: + if "major" in version_increments: + majorText = int(majorText) + 1 + minorText = 0 + patchText = 0 + elif "minor" in version_increments: + minorText = int(minorText) + 1 + patchText = 0 + elif "patch" in version_increments: + patchText = int(patchText) + 1 + + # Always increment build + if "build" in version_increments: + buildText = int(buildText) + 1 + + print_green("Incrementing version to {}.{}.{}.{}".format(majorText,minorText,patchText,buildText)) + with open(scriptModPath, "w", newline="\n") as file: + file.writelines([ + "#define MAJOR {}\n".format(majorText), + "#define MINOR {}\n".format(minorText), + "#define PATCHLVL {}\n".format(patchText), + "#define BUILD {}\n".format(buildText) + ]) + if majorText: - versionStamp = "{major}.{minor}.{patchlvl}.{build}".format(major=majorText,minor=minorText,patchlvl=patchlvlText,build=buildText) + versionStamp = "{}.{}.{}.{}".format(majorText,minorText,patchText,buildText) else: print_error("A Critical file seems to be missing or inaccessible: {}".format(scriptModPath)) @@ -565,22 +588,15 @@ def get_project_version(): def replace_file(filePath, oldSubstring, newSubstring): - #Create temp file - fh, absPath = mkstemp() - with open(absPath,'w') as newFile: - with open(filePath) as oldFile: - for line in oldFile: - newFile.write(line.replace(oldSubstring, newSubstring)) - newFile.close() - #Remove original file - os.remove(filePath) - #Move new file - shutil.move(absPath, filePath) + for line in fileinput.input(filePath, inplace=True): + # Use stdout directly, print() adds newlines automatically + sys.stdout.write(line.replace(oldSubstring,newSubstring)) def set_version_in_files(): newVersion = project_version # MAJOR.MINOR.PATCH.BUILD - newVersionShort = newVersion[:-2] # MAJOR.MINOR.PATCH + newVersionArr = newVersion.split(".") + newVersionShort = ".".join((newVersionArr[0],newVersionArr[1],newVersionArr[2])) # MAJOR.MINOR.PATCH # Regex patterns pattern = re.compile(r"([\d]+\.[\d]+\.[\d]+\.[\d]+)") # MAJOR.MINOR.PATCH.BUILD @@ -598,28 +614,26 @@ def set_version_in_files(): f.close() if fileText: - # Search and save version stamp, search short if long not found - versionFound = re.findall(pattern, fileText) - if not versionFound: - versionFound = re.findall(patternShort, fileText) + # Version string files + # Search and save version stamp + versionsFound = re.findall(pattern, fileText) + re.findall(patternShort, fileText) + # Filter out sub-versions of other versions + versionsFound = [j for i, j in enumerate(versionsFound) if all(j not in k for k in versionsFound[i + 1:])] # Replace version stamp if any of the new version parts is higher than the one found - if versionFound: - # First item in the list findall returns - versionFound = versionFound[0] - - newVersionUsed = "" - # Use the same version length as the one found - if len(versionFound) == len(newVersion): - newVersionUsed = newVersion - if len(versionFound) == len(newVersionShort): - newVersionUsed = newVersionShort - - # Print change and modify the file if changed - if versionFound != newVersionUsed: - print_green("Changing version {} => {} in {}".format(versionFound, newVersionUsed, filePath)) - replace_file(filePath, versionFound, newVersionUsed) + for versionFound in versionsFound: + if versionFound: + # Use the same version length as the one found + newVersionUsed = "" # In case undefined + if versionFound.count(".") == newVersion.count("."): + newVersionUsed = newVersion + if versionFound.count(".") == newVersionShort.count("."): + newVersionUsed = newVersionShort + # Print change and modify the file if changed + if newVersionUsed and versionFound != newVersionUsed: + print_green("Changing version {} => {} in {}".format(versionFound, newVersionUsed, filePath)) + replace_file(filePath, versionFound, newVersionUsed) except WindowsError as e: # Temporary file is still "in use" by Python, pass this exception pass @@ -634,13 +648,17 @@ def stash_version_files_for_building(): try: for file in versionFiles: filePath = os.path.join(module_root_parent, file) - # Take only file name for stash location if in subfolder (otherwise it gets removed when removing folders from release dir) - if "\\" in file: - count = file.count("\\") - file = file.split("\\", count)[-1] - stashPath = os.path.join(release_dir, file) - print("Temporarily stashing {} => {}.bak for version update".format(filePath, stashPath)) - shutil.copy(filePath, "{}.bak".format(stashPath)) + if os.path.exists(filePath): + # Take only file name for stash location if in subfolder (otherwise it gets removed when removing folders from release dir) + if "\\" in file: + count = file.count("\\") + file = file.split("\\", count)[-1] + stashPath = os.path.join(release_dir, file) + print("Temporarily stashing {} => {}.bak for version update".format(filePath, stashPath)) + shutil.copy(filePath, "{}.bak".format(stashPath)) + else: + print_error("Failed temporarily stashing {} for version update".format(filePath)) + missingFiles.append("{}".format(filePath)) except: print_error("Stashing version files failed") raise @@ -652,6 +670,8 @@ def stash_version_files_for_building(): def restore_version_files(): try: + print_blue("\nRestoring version files...") + for file in versionFiles: filePath = os.path.join(module_root_parent, file) # Take only file name for stash path if in subfolder (otherwise it gets removed when removing folders from release dir) @@ -659,8 +679,9 @@ def restore_version_files(): count = file.count("\\") file = file.split("\\", count)[-1] stashPath = os.path.join(release_dir, file) - print("Restoring {}".format(filePath)) - shutil.move("{}.bak".format(stashPath), filePath) + if os.path.exists(filePath): + print("Restoring {}".format(filePath)) + shutil.move("{}.bak".format(stashPath), filePath) except: print_error("Restoring version files failed") raise @@ -669,9 +690,9 @@ def restore_version_files(): def get_private_keyname(commitID,module="main"): global pbo_name_prefix + global project_version - aceVersion = get_project_version() - keyName = str("{prefix}{version}-{commit_id}".format(prefix=pbo_name_prefix,version=aceVersion,commit_id=commitID)) + keyName = str("{prefix}{version}-{commit_id}".format(prefix=pbo_name_prefix,version=project_version,commit_id=commitID)) return keyName @@ -786,6 +807,7 @@ def main(argv): global prefix global pbo_name_prefix global ciBuild + global missingFiles if sys.platform != "win32": print_error("Non-Windows platform (Cygwin?). Please re-run from cmd.") @@ -854,12 +876,9 @@ See the make.cfg file for additional build options. if "release" in argv: make_release_zip = True - release_version = argv[argv.index("release") + 1] - argv.remove(release_version) argv.remove("release") else: make_release_zip = False - release_version = project_version if "target" in argv: make_target = argv[argv.index("target") + 1] @@ -889,8 +908,22 @@ See the make.cfg file for additional build options. else: version_update = False - if "--ci" in argv: - argv.remove("--ci") + version_increments = [] + if "increment_build" in argv: + argv.remove("increment_build") + version_increments.append("build") + if "increment_patch" in argv: + argv.remove("increment_patch") + version_increments.append("patch") + if "increment_minor" in argv: + argv.remove("increment_minor") + version_increments.append("minor") + if "increment_major" in argv: + argv.remove("increment_major") + version_increments.append("major") + + if "ci" in argv: + argv.remove("ci") ciBuild = True print_yellow("\nCheck external references is set to {}".format(str(check_external))) @@ -956,21 +989,21 @@ See the make.cfg file for additional build options. optionals_root = os.path.join(module_root_parent, "optionals") extensions_root = os.path.join(module_root_parent, "extensions") - commit_id = get_commit_ID() - key_name = versionStamp = get_private_keyname(commit_id) - print_green ("module_root: {}".format(module_root)) - if (os.path.isdir(module_root)): os.chdir(module_root) else: print_error ("Directory {} does not exist.".format(module_root)) - sys.exit() + sys.exit(1) + + commit_id = get_commit_ID() + get_project_version(version_increments) + key_name = versionStamp = get_private_keyname(commit_id) + print_green ("module_root: {}".format(module_root)) if (os.path.isdir(optionals_root)): print_green ("optionals_root: {}".format(optionals_root)) else: - print_error ("Directory {} does not exist.".format(optionals_root)) - sys.exit() + print("optionals_root does not exist: {}".format(optionals_root)) print_green ("release_dir: {}".format(release_dir)) @@ -1022,7 +1055,6 @@ See the make.cfg file for additional build options. cache = {} # Check the build version (from main) with cached version - forces a full rebuild when version changes - project_version = get_project_version() cacheVersion = "None"; if 'cacheVersion' in cache: cacheVersion = cache['cacheVersion'] @@ -1046,6 +1078,9 @@ See the make.cfg file for additional build options. print_error("Cannot create release directory") raise + failedBuilds = [] + missingFiles = [] + # Update version stamp in all files that contain it # Update version only for release if full update not requested (backup and restore files) print_blue("\nChecking for obsolete version numbers...") @@ -1056,14 +1091,12 @@ See the make.cfg file for additional build options. set_version_in_files(); print("Version in files has been changed, make sure you commit and push the updates!") - amountOfBuildsFailed = 0 - namesOfBuildsFailed = [] - try: # Temporarily copy optionals_root for building. They will be removed later. - optionals_modules = [] - optional_files = [] - copy_optionals_for_building(optionals_modules,optional_files) + if (os.path.isdir(optionals_root)): + optionals_modules = [] + optional_files = [] + copy_optionals_for_building(optionals_modules,optional_files) # Get list of subdirs in make root. dirs = next(os.walk(module_root))[1] @@ -1128,15 +1161,14 @@ 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 check_for_obsolete_pbos(module_root, file): fileName = os.path.splitext(file)[0] - print_yellow("Removing obsolete file => {}".format(file)) + print_yellow("Removing obsolete pbo => {}".format(file)) purge(obsolete_check_path, "{}\..".format(fileName), "{}.*".format(fileName)) obsolete_check_path = os.path.join(module_root, release_dir, project) for file in os.listdir(obsolete_check_path): if (file.endswith(".dll") and os.path.isfile(os.path.join(obsolete_check_path,file))): - if check_for_obsolete_pbos(extensions_root, file): - fileName = os.path.splitext(file)[0] - print_yellow("Removing obsolete file => {}".format(file)) + if not os.path.exists(os.path.join(module_root_parent, file)): + print_yellow("Removing obsolete dll => {}".format(file)) try: os.remove(os.path.join(obsolete_check_path,file)) except: @@ -1282,8 +1314,7 @@ See the make.cfg file for additional build options. print_error("pboProject return code == {}".format(str(ret))) print_error("Module not successfully built/signed. Check your {}temp\{}_packing.log for more info.".format(work_drive,module)) print ("Resuming build...") - amountOfBuildsFailed += 1 - namesOfBuildsFailed.append("{}".format(module)) + failedBuilds.append("{}".format(module)) continue # Back to the root @@ -1379,7 +1410,8 @@ See the make.cfg file for additional build options. finally: copy_important_files(module_root_parent,os.path.join(release_dir, project)) - cleanup_optionals(optionals_modules) + if (os.path.isdir(optionals_root)): + cleanup_optionals(optionals_modules) if not version_update: restore_version_files() @@ -1399,8 +1431,7 @@ See the make.cfg file for additional build options. # Make release if make_release_zip: - release_name = "{}_{}".format(zipPrefix, release_version) - print_blue("\nMaking release: {}.zip".format(release_name)) + release_name = "{}_{}".format(zipPrefix, project_version) try: # Delete all log files @@ -1415,7 +1446,10 @@ See the make.cfg file for additional build options. os.remove(os.path.join(release_dir, file)) # Create a zip with the contents of release folder in it + print_blue("\nMaking release: {}.zip ...".format(release_name)) + print("Packing...") release_zip = shutil.make_archive("{}".format(release_name), "zip", release_dir) + # Move release zip to release folder shutil.copy(release_zip, release_dir) os.remove(release_zip) @@ -1450,15 +1484,23 @@ See the make.cfg file for additional build options. except: print_error("Could not copy files. Is Arma 3 running?") - if amountOfBuildsFailed > 0: - print_error("Build failed. {} pbos failed.".format(amountOfBuildsFailed)) + if len(failedBuilds) > 0 or len(missingFiles) > 0: + if len(failedBuilds) > 0: + print() + print_error("Build failed! {} PBOs failed!".format(len(failedBuilds))) + for failedBuild in failedBuilds: + print("- {} failed.".format(failedBuild)) - for failedModuleName in namesOfBuildsFailed: - print("- {} failed.".format(failedModuleName)) + if len(missingFiles) > 0: + missingFiles = set(missingFiles) + print() + print_error("Missing files! {} files not found!".format(len(missingFiles))) + for missingFile in missingFiles: + print("- {} failed.".format(missingFile)) sys.exit(1) else: - print_green("\Completed with 0 errors.") + print_green("\nCompleted with 0 errors.") if __name__ == "__main__": start_time = timeit.default_timer()