Add Arma CI Workflow (validate, lint, build) (#7207)

* No sqflint failures for now due to false-positives
* Upload artifact of armake build
* Cleanup Circle CI configuration
* Add BOM check
* Cleanup BOM
This commit is contained in:
jonpas 2019-10-05 22:53:16 +02:00 committed by GitHub
parent cec15a6f1a
commit 5a5d4758a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 125 additions and 118 deletions

45
.github/workflows/arma.yml vendored Normal file
View File

@ -0,0 +1,45 @@
name: Arma
on:
push:
branches:
- master
pull_request:
jobs:
validate:
runs-on: ubuntu-latest
steps:
- name: Checkout the source code
uses: actions/checkout@master
- name: Validate SQF
run: python3 tools/sqf_validator.py
- name: Validate Config
run: python3 tools/config_style_checker.py
- name: Validate String Tables
run: python3 tools/check_strings.py
- name: Check for BOM
uses: arma-actions/bom-check@master
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout the source code
uses: actions/checkout@master
- name: Lint (sqflint)
uses: arma-actions/sqflint@master
continue-on-error: true # No failure due to many false-positives
build:
runs-on: ubuntu-latest
container: acemod/armake:master
steps:
- name: Checkout the source code
uses: actions/checkout@master
- name: Build (armake)
run: armake --version && make -j4
- name: Upload Artifact
uses: actions/upload-artifact@master
with:
name: ace3-${{ github.sha }}-nobin
path: '@ace'

View File

@ -1,36 +1,5 @@
version: 2 version: 2
jobs: jobs:
validate-scripts:
docker:
- image: acemod/sqflint:latest
steps:
- checkout
- run:
name: Validate SQF and Config style and Stringtable entries
command: python tools/sqf_validator.py && python tools/config_style_checker.py && python tools/check_strings.py
linting:
docker:
- image: acemod/sqflint:latest
steps:
- checkout
- run:
name: Lint sqf code
command: sqflint -d addons || true
armake:
docker:
- image: acemod/armake:master
steps:
- checkout
- run:
name: Version
command: armake --version
- run:
name: Build
command: |
make -j 4
update-docs: update-docs:
docker: docker:
- image: acemod/armake:latest - image: acemod/armake:latest
@ -49,14 +18,7 @@ workflows:
version: 2 version: 2
build-job: build-job:
jobs: jobs:
- linting
- validate-scripts
- armake:
requires:
- validate-scripts
- update-docs: - update-docs:
requires:
- armake
filters: filters:
branches: branches:
only: master only: master

View File

@ -1,4 +1,4 @@
--- ---
layout: wiki layout: wiki
title: M47 Dragon Missile title: M47 Dragon Missile
description: M47 Dragon description: M47 Dragon

View File

@ -1,4 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import fnmatch import fnmatch
import os import os
@ -9,105 +9,105 @@ import argparse
def get_private_declare(content): def get_private_declare(content):
priv_declared = [] priv_declared = []
srch = re.compile('private.*') srch = re.compile('private.*')
priv_srch_declared = srch.findall(content) priv_srch_declared = srch.findall(content)
priv_srch_declared = sorted(set(priv_srch_declared)) priv_srch_declared = sorted(set(priv_srch_declared))
priv_dec_str = ''.join(priv_srch_declared) priv_dec_str = ''.join(priv_srch_declared)
srch = re.compile('(?<![_a-zA-Z0-9])(_[a-zA-Z0-9]*?)[ ,\}\]\)";]') srch = re.compile('(?<![_a-zA-Z0-9])(_[a-zA-Z0-9]*?)[ ,\}\]\)";]')
priv_split = srch.findall(priv_dec_str) priv_split = srch.findall(priv_dec_str)
priv_split = sorted(set(priv_split)) priv_split = sorted(set(priv_split))
priv_declared += priv_split; priv_declared += priv_split;
srch = re.compile('params \[.*\]|PARAMS_[0-9].*|EXPLODE_[0-9]_PVT.*|DEFAULT_PARAM.*|KEY_PARAM.*|IGNORE_PRIVATE_WARNING.*') srch = re.compile('params \[.*\]|PARAMS_[0-9].*|EXPLODE_[0-9]_PVT.*|DEFAULT_PARAM.*|KEY_PARAM.*|IGNORE_PRIVATE_WARNING.*')
priv_srch_declared = srch.findall(content) priv_srch_declared = srch.findall(content)
priv_srch_declared = sorted(set(priv_srch_declared)) priv_srch_declared = sorted(set(priv_srch_declared))
priv_dec_str = ''.join(priv_srch_declared) priv_dec_str = ''.join(priv_srch_declared)
srch = re.compile('(?<![_a-zA-Z0-9])(_[a-zA-Z0-9]*?)[ ,\}\]\)";]') srch = re.compile('(?<![_a-zA-Z0-9])(_[a-zA-Z0-9]*?)[ ,\}\]\)";]')
priv_split = srch.findall(priv_dec_str) priv_split = srch.findall(priv_dec_str)
priv_split = sorted(set(priv_split)) priv_split = sorted(set(priv_split))
priv_declared += priv_split; priv_declared += priv_split;
srch = re.compile('(?i)[\s]*local[\s]+(_[\w\d]*)[\s]*=.*') srch = re.compile('(?i)[\s]*local[\s]+(_[\w\d]*)[\s]*=.*')
priv_local = srch.findall(content) priv_local = srch.findall(content)
priv_local_declared = sorted(set(priv_local)) priv_local_declared = sorted(set(priv_local))
priv_declared += priv_local_declared; priv_declared += priv_local_declared;
return priv_declared return priv_declared
def check_privates(filepath): def check_privates(filepath):
bad_count_file = 0 bad_count_file = 0
def pushClosing(t): def pushClosing(t):
closingStack.append(closing.expr) closingStack.append(closing.expr)
closing << Literal( closingFor[t[0]] ) closing << Literal( closingFor[t[0]] )
def popClosing(): def popClosing():
closing << closingStack.pop() closing << closingStack.pop()
with open(filepath, 'r') as file: with open(filepath, 'r') as file:
content = file.read() content = file.read()
priv_use = [] priv_use = []
priv_use = [] priv_use = []
# Regex search privates # Regex search privates
srch = re.compile('(?<![_a-zA-Z0-9])(_[a-zA-Z0-9]*?)[ =,\^\-\+\/\*\%\}\]\)";]') srch = re.compile('(?<![_a-zA-Z0-9])(_[a-zA-Z0-9]*?)[ =,\^\-\+\/\*\%\}\]\)";]')
priv_use = srch.findall(content) priv_use = srch.findall(content)
priv_use = sorted(set(priv_use)) priv_use = sorted(set(priv_use))
# Private declaration search # Private declaration search
priv_declared = get_private_declare(content) priv_declared = get_private_declare(content)
if '_this' in priv_declared: priv_declared.remove('_this') if '_this' in priv_declared: priv_declared.remove('_this')
if '_this' in priv_use: priv_use.remove('_this') if '_this' in priv_use: priv_use.remove('_this')
if '_x' in priv_declared: priv_declared.remove('_x') if '_x' in priv_declared: priv_declared.remove('_x')
if '_x' in priv_use: priv_use.remove('_x') if '_x' in priv_use: priv_use.remove('_x')
if '_forEachIndex' in priv_declared: priv_declared.remove('_forEachIndex') if '_forEachIndex' in priv_declared: priv_declared.remove('_forEachIndex')
if '_forEachIndex' in priv_use: priv_use.remove('_forEachIndex') if '_forEachIndex' in priv_use: priv_use.remove('_forEachIndex')
if '_foreachIndex' in priv_declared: priv_declared.remove('_foreachIndex') if '_foreachIndex' in priv_declared: priv_declared.remove('_foreachIndex')
if '_foreachIndex' in priv_use: priv_use.remove('_foreachIndex') if '_foreachIndex' in priv_use: priv_use.remove('_foreachIndex')
if '_foreachindex' in priv_declared: priv_declared.remove('_foreachindex') if '_foreachindex' in priv_declared: priv_declared.remove('_foreachindex')
if '_foreachindex' in priv_use: priv_use.remove('_foreachindex') if '_foreachindex' in priv_use: priv_use.remove('_foreachindex')
missing = [] missing = []
for s in priv_use: for s in priv_use:
if s.lower() not in map(str.lower,priv_declared): if s.lower() not in map(str.lower,priv_declared):
if s.lower() not in map(str.lower,missing): if s.lower() not in map(str.lower,missing):
missing.append(s) missing.append(s)
if len(missing) > 0: if len(missing) > 0:
print (filepath) print (filepath)
private_output = 'private['; private_output = 'private[';
first = True first = True
for bad_priv in missing: for bad_priv in missing:
if first: if first:
first = False first = False
private_output = private_output + '"' + bad_priv private_output = private_output + '"' + bad_priv
else: else:
private_output = private_output + '", "' + bad_priv private_output = private_output + '", "' + bad_priv
private_output = private_output + '"];'; private_output = private_output + '"];';
print (private_output) print (private_output)
for bad_priv in missing: for bad_priv in missing:
print ('\t' + bad_priv) print ('\t' + bad_priv)
bad_count_file = bad_count_file + 1 bad_count_file = bad_count_file + 1
return bad_count_file return bad_count_file
def main(): def main():
print("#########################") print("#########################")
@ -116,20 +116,20 @@ def main():
sqf_list = [] sqf_list = []
bad_count = 0 bad_count = 0
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('-m','--module', help='only search specified module addon folder', required=False, default=".") parser.add_argument('-m','--module', help='only search specified module addon folder', required=False, default=".")
args = parser.parse_args() args = parser.parse_args()
for root, dirnames, filenames in os.walk('../addons' + '/' + args.module): for root, dirnames, filenames in os.walk('../addons' + '/' + args.module):
for filename in fnmatch.filter(filenames, '*.sqf'): for filename in fnmatch.filter(filenames, '*.sqf'):
sqf_list.append(os.path.join(root, filename)) sqf_list.append(os.path.join(root, filename))
for filename in sqf_list: for filename in sqf_list:
bad_count = bad_count + check_privates(filename) bad_count = bad_count + check_privates(filename)
print ("Bad Count {0}".format(bad_count)) print ("Bad Count {0}".format(bad_count))
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@ -1,4 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import fnmatch import fnmatch
import os import os

View File

@ -1,4 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import fnmatch import fnmatch
import os import os
@ -9,98 +9,98 @@ import argparse
def get_private_declare(content): def get_private_declare(content):
priv_declared = [] priv_declared = []
srch = re.compile('private.*') srch = re.compile('private.*')
priv_srch_declared = srch.findall(content) priv_srch_declared = srch.findall(content)
priv_srch_declared = sorted(set(priv_srch_declared)) priv_srch_declared = sorted(set(priv_srch_declared))
priv_dec_str = ''.join(priv_srch_declared) priv_dec_str = ''.join(priv_srch_declared)
srch = re.compile('(?<![_a-zA-Z0-9])(_[a-zA-Z]*?)[ ,\}\]\)";]') srch = re.compile('(?<![_a-zA-Z0-9])(_[a-zA-Z]*?)[ ,\}\]\)";]')
priv_split = srch.findall(priv_dec_str) priv_split = srch.findall(priv_dec_str)
priv_split = sorted(set(priv_split)) priv_split = sorted(set(priv_split))
priv_declared += priv_split; priv_declared += priv_split;
srch = re.compile('params \[.*\]|PARAMS_[0-9].*|EXPLODE_[0-9]_PVT.*|DEFAULT_PARAM.*|KEY_PARAM.*|IGNORE_PRIVATE_WARNING.*') srch = re.compile('params \[.*\]|PARAMS_[0-9].*|EXPLODE_[0-9]_PVT.*|DEFAULT_PARAM.*|KEY_PARAM.*|IGNORE_PRIVATE_WARNING.*')
priv_srch_declared = srch.findall(content) priv_srch_declared = srch.findall(content)
priv_srch_declared = sorted(set(priv_srch_declared)) priv_srch_declared = sorted(set(priv_srch_declared))
priv_dec_str = ''.join(priv_srch_declared) priv_dec_str = ''.join(priv_srch_declared)
srch = re.compile('(?<![_a-zA-Z0-9])(_[a-zA-Z]*?)[ ,\}\]\)";]') srch = re.compile('(?<![_a-zA-Z0-9])(_[a-zA-Z]*?)[ ,\}\]\)";]')
priv_split = srch.findall(priv_dec_str) priv_split = srch.findall(priv_dec_str)
priv_split = sorted(set(priv_split)) priv_split = sorted(set(priv_split))
priv_declared += priv_split; priv_declared += priv_split;
return priv_declared return priv_declared
def check_privates(filepath): def check_privates(filepath):
bad_count_file = 0 bad_count_file = 0
def pushClosing(t): def pushClosing(t):
closingStack.append(closing.expr) closingStack.append(closing.expr)
closing << Literal( closingFor[t[0]] ) closing << Literal( closingFor[t[0]] )
def popClosing(): def popClosing():
closing << closingStack.pop() closing << closingStack.pop()
with open(filepath, 'r') as file: with open(filepath, 'r') as file:
content = file.read() content = file.read()
priv_use = [] priv_use = []
priv_use = [] priv_use = []
# Regex search privates # Regex search privates
srch = re.compile('(?<![_a-zA-Z0-9])(_[a-zA-Z]*?)[ ,\}\]\)";]') srch = re.compile('(?<![_a-zA-Z0-9])(_[a-zA-Z]*?)[ ,\}\]\)";]')
priv_use = srch.findall(content) priv_use = srch.findall(content)
# Private declaration search # Private declaration search
priv_declared = get_private_declare(content) priv_declared = get_private_declare(content)
if '_this' in priv_declared: priv_declared.remove('_this') if '_this' in priv_declared: priv_declared.remove('_this')
if '_this' in priv_use: priv_use.remove('_this') if '_this' in priv_use: priv_use.remove('_this')
if '_x' in priv_declared: priv_declared.remove('_x') if '_x' in priv_declared: priv_declared.remove('_x')
if '_x' in priv_use: priv_use.remove('_x') if '_x' in priv_use: priv_use.remove('_x')
if '_forEachIndex' in priv_declared: priv_declared.remove('_forEachIndex') if '_forEachIndex' in priv_declared: priv_declared.remove('_forEachIndex')
if '_forEachIndex' in priv_use: priv_use.remove('_forEachIndex') if '_forEachIndex' in priv_use: priv_use.remove('_forEachIndex')
if '_foreachIndex' in priv_declared: priv_declared.remove('_foreachIndex') if '_foreachIndex' in priv_declared: priv_declared.remove('_foreachIndex')
if '_foreachIndex' in priv_use: priv_use.remove('_foreachIndex') if '_foreachIndex' in priv_use: priv_use.remove('_foreachIndex')
if '_foreachindex' in priv_declared: priv_declared.remove('_foreachindex') if '_foreachindex' in priv_declared: priv_declared.remove('_foreachindex')
if '_foreachindex' in priv_use: priv_use.remove('_foreachindex') if '_foreachindex' in priv_use: priv_use.remove('_foreachindex')
unused = [] unused = []
for s in priv_declared: for s in priv_declared:
if priv_use.count(s) == 1: if priv_use.count(s) == 1:
if s.lower() not in map(str.lower,unused): if s.lower() not in map(str.lower,unused):
unused.append(s) unused.append(s)
if len(unused) > 0: if len(unused) > 0:
print (filepath) print (filepath)
private_output = 'private['; private_output = 'private[';
first = True first = True
for bad_priv in unused: for bad_priv in unused:
if first: if first:
first = False first = False
private_output = private_output + '"' + bad_priv private_output = private_output + '"' + bad_priv
else: else:
private_output = private_output + '", "' + bad_priv private_output = private_output + '", "' + bad_priv
private_output = private_output + '"];'; private_output = private_output + '"];';
print (private_output) print (private_output)
for bad_priv in unused: for bad_priv in unused:
print ('\t' + bad_priv) print ('\t' + bad_priv)
bad_count_file = bad_count_file + 1 bad_count_file = bad_count_file + 1
return bad_count_file return bad_count_file
def main(): def main():
print("#########################") print("#########################")
@ -109,20 +109,20 @@ def main():
sqf_list = [] sqf_list = []
bad_count = 0 bad_count = 0
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('-m','--module', help='only search specified module addon folder', required=False, default=".") parser.add_argument('-m','--module', help='only search specified module addon folder', required=False, default=".")
args = parser.parse_args() args = parser.parse_args()
for root, dirnames, filenames in os.walk('../addons' + '/' + args.module): for root, dirnames, filenames in os.walk('../addons' + '/' + args.module):
for filename in fnmatch.filter(filenames, '*.sqf'): for filename in fnmatch.filter(filenames, '*.sqf'):
sqf_list.append(os.path.join(root, filename)) sqf_list.append(os.path.join(root, filename))
for filename in sqf_list: for filename in sqf_list:
bad_count = bad_count + check_privates(filename) bad_count = bad_count + check_privates(filename)
print ("Bad Count {0}".format(bad_count)) print ("Bad Count {0}".format(bad_count))
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@ -1,4 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# Requires: https://github.com/LordGolias/sqf # Requires: https://github.com/LordGolias/sqf
@ -24,7 +24,7 @@ def analyze(filename, writer=sys.stdout):
return 0, 1 return 0, 1
exceptions = sqf.analyzer.analyze(result).exceptions exceptions = sqf.analyzer.analyze(result).exceptions
if (exceptions): if (exceptions):
print("{}:".format(filename)) print("{}:".format(filename))
for e in exceptions: for e in exceptions:
if (e.message.startswith("error")): if (e.message.startswith("error")):
@ -32,9 +32,9 @@ def analyze(filename, writer=sys.stdout):
else: else:
warnings += 1 warnings += 1
writer.write(' [%d,%d]:%s\n' % (e.position[0], e.position[1] - 1, e.message)) writer.write(' [%d,%d]:%s\n' % (e.position[0], e.position[1] - 1, e.message))
return warnings, errors return warnings, errors
def main(): def main():
print("#########################") print("#########################")
print("# Lint Check #") print("# Lint Check #")
@ -43,24 +43,24 @@ def main():
sqf_list = [] sqf_list = []
all_warnings = 0 all_warnings = 0
all_errors = 0 all_errors = 0
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('-m','--module', help='only search specified module addon folder', required=False, default=".") parser.add_argument('-m','--module', help='only search specified module addon folder', required=False, default=".")
args = parser.parse_args() args = parser.parse_args()
for root, dirnames, filenames in os.walk('../addons' + '/' + args.module): for root, dirnames, filenames in os.walk('../addons' + '/' + args.module):
for filename in fnmatch.filter(filenames, '*.sqf'): for filename in fnmatch.filter(filenames, '*.sqf'):
sqf_list.append(os.path.join(root, filename)) sqf_list.append(os.path.join(root, filename))
for filename in sqf_list: for filename in sqf_list:
warnings, errors = analyze(filename) warnings, errors = analyze(filename)
all_warnings += warnings all_warnings += warnings
all_errors += errors all_errors += errors
print ("Parse Errors {0} - Warnings {1}".format(all_errors,all_warnings)) print ("Parse Errors {0} - Warnings {1}".format(all_errors,all_warnings))
# return (all_errors + all_warnings) # return (all_errors + all_warnings)
return all_errors return all_errors
if __name__ == "__main__": if __name__ == "__main__":
main() main()