From 784b2ecb32f013e15f70bbff21e2354dba2e2aaa Mon Sep 17 00:00:00 2001 From: tbeu Date: Wed, 21 Feb 2024 20:20:59 +0100 Subject: [PATCH] Setup CI checks via GitHub Actions --- .CI/Test/ModelicaUtilities.h | 222 +++++++++++++++++++++++ .CI/checkHTMLDoc/__init__.py | 2 + .CI/checkHTMLDoc/checkLinks.py | 95 ++++++++++ .CI/checkHTMLDoc/checkTags.py | 103 +++++++++++ .CI/checkHTMLDoc/tidyHTML.py | 99 ++++++++++ .CI/check_deprecated_line_color.py | 60 ++++++ .CI/check_html.py | 36 ++++ .github/checkTags.json | 16 ++ .github/check_deprecated_line_color.json | 16 ++ .github/moparser.json | 16 ++ .github/tidyHTML.json | 18 ++ .github/workflows/checkCI.yml | 130 +++++++++++++ README.md | 2 +- 13 files changed, 814 insertions(+), 1 deletion(-) create mode 100644 .CI/Test/ModelicaUtilities.h create mode 100644 .CI/checkHTMLDoc/__init__.py create mode 100644 .CI/checkHTMLDoc/checkLinks.py create mode 100644 .CI/checkHTMLDoc/checkTags.py create mode 100644 .CI/checkHTMLDoc/tidyHTML.py create mode 100644 .CI/check_deprecated_line_color.py create mode 100644 .CI/check_html.py create mode 100644 .github/checkTags.json create mode 100644 .github/check_deprecated_line_color.json create mode 100644 .github/moparser.json create mode 100644 .github/tidyHTML.json create mode 100644 .github/workflows/checkCI.yml diff --git a/.CI/Test/ModelicaUtilities.h b/.CI/Test/ModelicaUtilities.h new file mode 100644 index 0000000..1dfa97c --- /dev/null +++ b/.CI/Test/ModelicaUtilities.h @@ -0,0 +1,222 @@ +/* ModelicaUtilities.h - External utility functions header + + Copyright (C) 2010-2024, Modelica Association and contributors + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/* Utility functions which can be called by external Modelica functions. + + These functions are defined in section 12.9.6 of the Modelica Specification 3.6. + + A generic C-implementation of these functions cannot be given, + because it is tool dependent how strings are output in a + console or window of the respective simulation tool. +*/ + +#ifndef MODELICA_UTILITIES_H +#define MODELICA_UTILITIES_H + +#include +#include + +#if defined(__cplusplus) +extern "C" { +#endif + +/* + Some of the functions never return to the caller. In order to compile + external Modelica C-code in most compilers, noreturn attributes need to + be present to avoid warnings or errors. + + The following macros handle noreturn attributes according to the + C11/C++11 standard with fallback to GNU, Clang or MSVC extensions if using + an older compiler. +*/ +#undef MODELICA_NORETURN +#undef MODELICA_NORETURNATTR +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L +#define MODELICA_NORETURN _Noreturn +#define MODELICA_NORETURNATTR +#elif defined(__cplusplus) && __cplusplus >= 201103L +#if (defined(__GNUC__) && __GNUC__ >= 5) || \ + (defined(__GNUC__) && defined(__GNUC_MINOR__) && __GNUC__ == 4 && __GNUC_MINOR__ >= 8) +#define MODELICA_NORETURN [[noreturn]] +#define MODELICA_NORETURNATTR +#elif (defined(__GNUC__) && __GNUC__ >= 3) || \ + (defined(__GNUC__) && defined(__GNUC_MINOR__) && __GNUC__ == 2 && __GNUC_MINOR__ >= 8) +#define MODELICA_NORETURN +#define MODELICA_NORETURNATTR __attribute__((noreturn)) +#elif defined(__GNUC__) +#define MODELICA_NORETURN +#define MODELICA_NORETURNATTR +#else +#define MODELICA_NORETURN [[noreturn]] +#define MODELICA_NORETURNATTR +#endif +#elif defined(__clang__) +/* Encapsulated for Clang since GCC fails to process __has_attribute */ +#if __has_attribute(noreturn) +#define MODELICA_NORETURN +#define MODELICA_NORETURNATTR __attribute__((noreturn)) +#else +#define MODELICA_NORETURN +#define MODELICA_NORETURNATTR +#endif +#elif (defined(__GNUC__) && __GNUC__ >= 3) || \ + (defined(__GNUC__) && defined(__GNUC_MINOR__) && __GNUC__ == 2 && __GNUC_MINOR__ >= 8) || \ + (defined(__SUNPRO_C) && __SUNPRO_C >= 0x5110) +#define MODELICA_NORETURN +#define MODELICA_NORETURNATTR __attribute__((noreturn)) +#elif (defined(_MSC_VER) && _MSC_VER >= 1200) || \ + defined(__BORLANDC__) +#define MODELICA_NORETURN __declspec(noreturn) +#define MODELICA_NORETURNATTR +#else +#define MODELICA_NORETURN +#define MODELICA_NORETURNATTR +#endif + +/* + The following macros handle format attributes for type-checks against a + format string. +*/ + +#if defined(__clang__) +/* Encapsulated for Clang since GCC fails to process __has_attribute */ +#if __has_attribute(format) +#define MODELICA_FORMATATTR_PRINTF __attribute__((format(printf, 1, 2))) +#define MODELICA_FORMATATTR_VPRINTF __attribute__((format(printf, 1, 0))) +#else +#define MODELICA_FORMATATTR_PRINTF +#define MODELICA_FORMATATTR_VPRINTF +#endif +#elif defined(__GNUC__) && __GNUC__ >= 3 +#define MODELICA_FORMATATTR_PRINTF __attribute__((format(printf, 1, 2))) +#define MODELICA_FORMATATTR_VPRINTF __attribute__((format(printf, 1, 0))) +#else +#define MODELICA_FORMATATTR_PRINTF +#define MODELICA_FORMATATTR_VPRINTF +#endif + +void ModelicaMessage(const char *string); +/* +Output the message string (no format control). +*/ + +void ModelicaFormatMessage(const char *format, ...) MODELICA_FORMATATTR_PRINTF; +/* +Output the message under the same format control as the C-function printf. +*/ + +void ModelicaVFormatMessage(const char *format, va_list args) MODELICA_FORMATATTR_VPRINTF; +/* +Output the message under the same format control as the C-function vprintf. +*/ + +MODELICA_NORETURN void ModelicaError(const char *string) MODELICA_NORETURNATTR; +/* +Output the error message string (no format control). This function +never returns to the calling function, but handles the error +similarly to an assert in the Modelica code. +*/ + +void ModelicaWarning(const char *string); +/* +Output the warning message string (no format control). +*/ + +void ModelicaFormatWarning(const char *format, ...) MODELICA_FORMATATTR_PRINTF; +/* +Output the warning message under the same format control as the C-function printf. +*/ + +void ModelicaVFormatWarning(const char *format, va_list args) MODELICA_FORMATATTR_VPRINTF; +/* +Output the warning message under the same format control as the C-function vprintf. +*/ + +MODELICA_NORETURN void ModelicaFormatError(const char *format, ...) MODELICA_NORETURNATTR MODELICA_FORMATATTR_PRINTF; +/* +Output the error message under the same format control as the C-function +printf. This function never returns to the calling function, +but handles the error similarly to an assert in the Modelica code. +*/ + +MODELICA_NORETURN void ModelicaVFormatError(const char *format, va_list args) MODELICA_NORETURNATTR MODELICA_FORMATATTR_VPRINTF; +/* +Output the error message under the same format control as the C-function +vprintf. This function never returns to the calling function, +but handles the error similarly to an assert in the Modelica code. +*/ + +char* ModelicaAllocateString(size_t len); +/* +Allocate memory for a Modelica string which is used as return +argument of an external Modelica function. Note, that the storage +for string arrays (= pointer to string array) is still provided by the +calling program, as for any other array. If an error occurs, this +function does not return, but calls "ModelicaError". +*/ + +char* ModelicaAllocateStringWithErrorReturn(size_t len); +/* +Same as ModelicaAllocateString, except that in case of error, the +function returns 0. This allows the external function to close files +and free other open resources in case of error. After cleaning up +resources use ModelicaError or ModelicaFormatError to signal +the error. +*/ + +char* ModelicaDuplicateString(const char* str); +/* +Duplicate (= allocate memory and deep copy) a Modelica string which +is used as return argument of an external Modelica function. Note, +that the storage for string arrays (= pointer to string array) is still +provided by the calling program, as for any other array. If an error +occurs, this function does not return, but calls "ModelicaError". +*/ + +char* ModelicaDuplicateStringWithErrorReturn(const char* str); +/* +Same as ModelicaDuplicateString, except that in case of error, the +function returns 0. This allows the external function to close files +and free other open resources in case of error. After cleaning up +resources use, ModelicaError or ModelicaFormatError to signal +the error. +*/ + +#undef MODELICA_NORETURN +#undef MODELICA_NORETURNATTR +#undef MODELICA_FORMATATTR_PRINTF +#undef MODELICA_FORMATATTR_VPRINTF + +#if defined(__cplusplus) +} +#endif + +#endif diff --git a/.CI/checkHTMLDoc/__init__.py b/.CI/checkHTMLDoc/__init__.py new file mode 100644 index 0000000..faa18be --- /dev/null +++ b/.CI/checkHTMLDoc/__init__.py @@ -0,0 +1,2 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- diff --git a/.CI/checkHTMLDoc/checkLinks.py b/.CI/checkHTMLDoc/checkLinks.py new file mode 100644 index 0000000..cc17288 --- /dev/null +++ b/.CI/checkHTMLDoc/checkLinks.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +''' +Copyright (C) 2020, Modelica Association and contributors +All rights reserved. + +Check Modelica HTML documentation for link validity +''' + +import re +import os +try: + import urllib2 +except ImportError: + import urllib.request as urllib2 +import ssl + +from concurrent.futures import ProcessPoolExecutor as PoolExecutor + +# See https://haacked.com/archive/2004/10/25/usingregularexpressionstomatchhtml.aspx/ +PATTERN = re.compile(r'\s]+))?)+\s*|\s*)/?>', + re.IGNORECASE) + +def _getFileURLs(file_name): + urls = [] + with open(file_name) as file: + i = 1 + for line in file: + for match in PATTERN.finditer(line): + tag = match.group(0) + tag = tag.strip('< >') + if tag.split(' ')[0].lower() != 'a': + continue + url = re.search(r'(?<=href=\\")http.*?(?=\\")', tag) + if url is None: + continue + url = url.group(0) + if url in urls: + continue + urls.append(url) + i = i + 1 + return {file_name: urls} + +def _getURLs(path): + urls = {} + for subdir, _, files in os.walk(path): + for file in files: + if os.path.splitext(file)[1] == '.mo': + file_name = os.path.join(subdir, file) + urls.update(_getFileURLs(file_name)) + return urls + +def _checkURL(url): + try: + rc = urllib2.urlopen(url).getcode() + return (url, rc) + except: + pass + try: + headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64)'} + rc = urllib2.urlopen(urllib2.Request(url, None, headers), context=ssl._create_unverified_context()).getcode() + except urllib2.HTTPError as e: + rc = e.code + if rc == 429: + # Ignore too many requests + rc = 200 + elif rc in (301, 302): + # Handle redirect errors + rc = urllib2.build_opener(urllib2.HTTPCookieProcessor).open(url).code + except: + rc = 0 + return (url, rc) + +def checkLinks(path): + if os.path.isdir(path): + urls = _getURLs(path) + elif os.path.isfile(path): + urls = _getFileURLs(path) + else: + return 1 + + all_urls = set() + for url_list in urls.values(): + all_urls.update(url_list) + + errors = 0 + with PoolExecutor(max_workers=8) as executor: + for url, rc in executor.map(_checkURL, all_urls): + if rc != 200: + errors = errors + 1 + # Only get first match for file name + file_name = next(file_name for file_name, url_list in urls.items() if url in url_list) + print('File "{0}": Error {1}for URL "{2}"'.format(file_name, '' if rc == 0 else str(rc) + ' ', url)) + return errors diff --git a/.CI/checkHTMLDoc/checkTags.py b/.CI/checkHTMLDoc/checkTags.py new file mode 100644 index 0000000..8e7c196 --- /dev/null +++ b/.CI/checkHTMLDoc/checkTags.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +''' +Copyright (C) 2020, Modelica Association and contributors +All rights reserved. + +Check Modelica HTML documentation for tag validity +''' + +import re +import os + +# See https://haacked.com/archive/2004/10/25/usingregularexpressionstomatchhtml.aspx/ +PATTERN = re.compile(r'\s]+))?)+\s*|\s*)/?>', + re.IGNORECASE) + +# See https://html.spec.whatwg.org/#void-elements +VOID_TAGS = ('area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'menuitem', 'meta', 'param', 'source', 'track', 'wbr') + +# Tags that should not be used +AVOIDABLE_TAGS = ('b', 'i', 'h1', 'h2', 'h3', 'h6', 'figure', 'figcaption') + +def _checkFileTags(file_name): + """ + Check each HTML documentation found in file_name for + - unclosed tags + - tag case sensitivity + - use of avoidable tags + + Known issue: Multi-line HTML tags are erroneously detected as error + """ + found_invalid = [] + found_mismatch = [] + found_case = [] + found_inline = [] + with open(file_name) as file: + stack = [] + i = 1 + for line in file: + for match in PATTERN.finditer(line): + tag = match.group(0) + tag = tag.strip('< >') + tag = tag.split(' ')[0] + if bool(stack): + if tag.lower() in AVOIDABLE_TAGS: + found_invalid.append((i, tag)) + if tag != tag.lower() and (bool(stack) or tag.lower() in ('html', '/html')): + found_case.append((i, tag)) + if tag.lower() == 'img' and len(stack) == 1: + found_inline.append((i, tag)) + if tag.lower() == 'html': + if bool(stack): + found_mismatch.append((i, tag)) + found_mismatch.append(stack[-1]) + stack = [] + stack.append((i, tag)) + elif tag.lower() == '/html': + if not bool(stack) or len(stack) != 1 or stack[-1][1].lower() != 'html': + found_mismatch.append((i, tag)) + found_mismatch.append(stack[-1]) + stack = [] + elif tag[0] != '/' and tag not in VOID_TAGS: + if bool(stack): + stack.append((i, tag)) + elif bool(stack) and tag[0] == '/': + if stack[-1][1] == tag[1:]: + del stack[-1] + else: + found_mismatch.append((i, tag)) + found_mismatch.append(stack[-1]) + i = i + 1 + if bool(stack): + found_mismatch.append(stack[-1]) + + # Debug print + for i, tag in found_mismatch: + print('File "{0}": line {1} - Warning: HTML tag "{2}" mismatch'.format(file_name, i, tag)) + for i, tag in found_invalid: + print('File "{0}": line {1} - Warning: HTML tag "{2}" misuse.'.format(file_name, i, tag)) + for i, tag in found_case: + print('File "{0}": line {1} - Warning: HTML tag "{2}" misspelling. Use lower case.'.format(file_name, i, tag)) + for i, tag in found_inline: + print('File "{0}": line {1} - Warning: HTML tag "{2}" misuse. Wrap in "div" tag.'.format(file_name, i, tag)) + + return len(found_mismatch) + len(found_invalid) + len(found_case) + len(found_inline) + +def _walkCheck(func, path): + error_count = 0 + for subdir, _, files in os.walk(path): + for file in files: + if os.path.splitext(file)[1] == '.mo': + file_name = os.path.join(subdir, file) + error_count = error_count + func(file_name) + return error_count + +def checkTags(path): + if os.path.isdir(path): + return _walkCheck(_checkFileTags, path) + elif os.path.isfile(path): + return _checkFileTags(path) + else: + return 1 diff --git a/.CI/checkHTMLDoc/tidyHTML.py b/.CI/checkHTMLDoc/tidyHTML.py new file mode 100644 index 0000000..663e140 --- /dev/null +++ b/.CI/checkHTMLDoc/tidyHTML.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +''' +Copyright (C) 2020, Modelica Association and contributors +All rights reserved. + +Check Modelica HTML documentation with HTML Tidy +''' + +import re +import os + +from tidylib import tidy_document + +# See https://haacked.com/archive/2004/10/25/usingregularexpressionstomatchhtml.aspx/ +PATTERN = re.compile(r'\s]+))?)+\s*|\s*)/?>', + re.IGNORECASE) + +# HTML Tidy IDs to be ignored +IGNORE_IDS = ('MISSING_ATTRIBUTE', 'MISMATCHED_ATTRIBUTE_WARN', 'REMOVED_HTML5') + +# HTML Tidy options +TIDY_OPTIONS = {'doctype': 'omit', 'show-body-only': 1, 'show-warnings': 1, 'show-info': 1, 'mute-id': 1} + +def _tidyHTML(doc): + body = ''.join(doc) + body = body.replace('\\"', '"') + + _, errors = tidy_document(r'{0}'.format(body), options=TIDY_OPTIONS) + + error_list = errors.rstrip().split('\n') + errors = [] + for error in error_list: + error = error.rstrip().split('\n') + for err in error: + if bool(err) and not any(id in err for id in IGNORE_IDS): + errors.append(err) + + return errors + +def _tidyFileHTML(file_name): + """ + Run HTML Tidy on each HTML documentation found in file_name + """ + errors = [] + with open(file_name) as file: + in_line = 0 + doc = [] + i = 1 + for line in file: + if in_line > 0: + doc.append(line) + for match in PATTERN.finditer(line): + tag = match.group(0) + tag = tag.strip('< >') + tag = tag.split(' ')[0] + tag = tag.lower() + if tag == 'html': + # Fill with empty lines to get matching line numbers from HTML Tidy + doc = ['\n']*(i - 1) + doc.append(line[match.end():]) + in_line = i + elif tag == '/html': + if in_line != i: + del doc[-1] + cont = line[:match.start()] + else: + # Single line + cont = doc.pop() + cont = cont[:(match.start() - len(line))] + doc.append(cont) + # Call HTML Tidy + errors = errors + _tidyHTML(doc) + in_line = 0 + i = i + 1 + + # Debug print + for error in errors: + print('File "{0}": {1}'.format(file_name, error)) + + return len(errors) + +def _walkCheck(func, path): + error_count = 0 + for subdir, _, files in os.walk(path): + for file in files: + if os.path.splitext(file)[1] == '.mo': + file_name = os.path.join(subdir, file) + error_count = error_count + func(file_name) + return error_count + +def tidyHTML(path): + if os.path.isdir(path): + return _walkCheck(_tidyFileHTML, path) + elif os.path.isfile(path): + return _tidyFileHTML(path) + else: + return 1 diff --git a/.CI/check_deprecated_line_color.py b/.CI/check_deprecated_line_color.py new file mode 100644 index 0000000..cf31b23 --- /dev/null +++ b/.CI/check_deprecated_line_color.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +''' +Copyright (C) 2021-2023, Modelica Association and contributors +All rights reserved. + +Check for deprecated Text.lineColor annotation +python check_deprecated_line_color.py [path...] +''' + +import os +import re +import sys + +PATTERN = re.compile(r'(?:Text\s*\([^\)]*?(?:\"(?:\\.|[^\"])*\")?[^\)]*?)(lineColor)') + +def _checkDeprecatedFileLineColor(file_name): + errors = 0 + with open(file_name, 'r') as file: + lines = file.readlines() + line_pos = [0] + n_lines = len(lines) + for i in range(n_lines): + line_pos.append(line_pos[-1] + len(lines[i])) + for match in PATTERN.finditer(''.join(lines)): + print('File "{0}": line {1} - Warning: Deprecated Text.' + 'lineColor annotation. Use Text.textColor.'.format( + file_name, next(i for i in range(n_lines) if line_pos[i] > match.start(1)))) + errors = errors + 1 + return errors + +def _walkCheck(func, path): + error_count = 0 + for subdir, _, files in os.walk(path): + for file in files: + if os.path.splitext(file)[1] == '.mo': + file_name = os.path.join(subdir, file) + error_count = error_count + func(file_name) + return error_count + +def checkDeprecatedLineColor(path): + if os.path.isdir(path): + return _walkCheck(_checkDeprecatedFileLineColor, path) + elif os.path.isfile(path): + return _checkDeprecatedFileLineColor(path) + else: + return 1 + +if __name__ == '__main__': + module_dir = os.path.split(__file__)[0] + if len(sys.argv) == 1: + paths = (os.path.realpath(module_dir), ) + elif len(sys.argv) > 1: + paths = sys.argv[1:] + + error_count = 0 + for path in paths: + error_count = error_count + checkDeprecatedLineColor(path) + sys.exit(error_count) diff --git a/.CI/check_html.py b/.CI/check_html.py new file mode 100644 index 0000000..dcd7f61 --- /dev/null +++ b/.CI/check_html.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +''' +Copyright (C) 2020, Modelica Association and contributors +All rights reserved. + +Check Modelica HTML documentation for validity +python check_html.py [check_function] [path] +''' + +import importlib +import os.path +import sys + +if __name__ == '__main__': + module_dir = os.path.split(__file__)[0] + if len(sys.argv) == 1: + function = 'checkTags' + path = os.path.realpath(module_dir) + elif len(sys.argv) == 2: + function = sys.argv[1] + path = os.path.realpath(module_dir) + elif len(sys.argv) > 2: + function = sys.argv[1] + path = sys.argv[2] + + try: + mod = importlib.import_module('checkHTMLDoc.' + function) + check_function = getattr(mod, function) + error_count = check_function(path) + except ImportError as e: + print(str(e)) + error_count = 1 + + sys.exit(error_count) diff --git a/.github/checkTags.json b/.github/checkTags.json new file mode 100644 index 0000000..c10b620 --- /dev/null +++ b/.github/checkTags.json @@ -0,0 +1,16 @@ +{ + "problemMatcher": [ + { + "owner": "checkTags", + "pattern": [ + { + "regexp": "^File\\s\"(?:\\.\\/)?([^\"]*)\":\\sline\\s(\\d+)\\s-\\s(Error|Warning|Info):\\s(.*)$", + "file": 1, + "line": 2, + "severity": 3, + "message": 4 + } + ] + } + ] +} diff --git a/.github/check_deprecated_line_color.json b/.github/check_deprecated_line_color.json new file mode 100644 index 0000000..fa6b647 --- /dev/null +++ b/.github/check_deprecated_line_color.json @@ -0,0 +1,16 @@ +{ + "problemMatcher": [ + { + "owner": "check_deprecated_line_color", + "pattern": [ + { + "regexp": "^File\\s\"(?:\\.\\/)?([^\"]*)\":\\sline\\s(\\d+)\\s-\\s(Error|Warning|Info):\\s(.*)$", + "file": 1, + "line": 2, + "severity": 3, + "message": 4 + } + ] + } + ] +} diff --git a/.github/moparser.json b/.github/moparser.json new file mode 100644 index 0000000..1eaf5f5 --- /dev/null +++ b/.github/moparser.json @@ -0,0 +1,16 @@ +{ + "problemMatcher": [ + { + "owner": "moparser", + "pattern": [ + { + "regexp": "^syntax\\s(error)\\son\\s\\line\\s(\\d+)\\sof\\s(.*):\\s(.*)$", + "file": 3, + "line": 2, + "severity": 1, + "message": 4 + } + ] + } + ] +} diff --git a/.github/tidyHTML.json b/.github/tidyHTML.json new file mode 100644 index 0000000..0bc4a49 --- /dev/null +++ b/.github/tidyHTML.json @@ -0,0 +1,18 @@ +{ + "problemMatcher": [ + { + "owner": "tidyHTML", + "pattern": [ + { + "regexp": "^File\\s\"(?:\\.\\/)?([^\"]*)\":\\sline\\s(\\d+)\\scolumn\\s(\\d+)\\s-\\s(Error|Warning|Info):\\s(.*)\\s\\((.*)\\)$", + "file": 1, + "line": 2, + "column": 3, + "severity": 4, + "message": 5, + "code": 6 + } + ] + } + ] +} diff --git a/.github/workflows/checkCI.yml b/.github/workflows/checkCI.yml new file mode 100644 index 0000000..2bbf4c2 --- /dev/null +++ b/.github/workflows/checkCI.yml @@ -0,0 +1,130 @@ +name: CI + +on: + push: + pull_request: + workflow_dispatch: + +jobs: + build: + name: build_cmake_${{ matrix.toolchain }} + runs-on: ${{ matrix.os }} + timeout-minutes: 5 + strategy: + fail-fast: false + matrix: + toolchain: + - linux-gcc + - macos-clang + - windows-msvc + - windows-mingw + configuration: + - Debug + include: + - toolchain: linux-gcc + os: ubuntu-latest + compiler: gcc + - toolchain: macos-clang + os: macos-latest + compiler: clang + - toolchain: windows-msvc + os: windows-latest + compiler: msvc + - toolchain: windows-mingw + os: windows-latest + compiler: mingw + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 5 + - name: Configure (${{ matrix.configuration }}) + run: | + if [ "${{ matrix.compiler }}" == "msvc" ]; then + cmake -S "$SRCDIR" -B build -DMODELICA_UTILITIES_INCLUDE_DIR="$TESTDIR" -DMODELICA_DEBUG_TIME_EVENTS=ON + elif [ "${{ matrix.compiler }}" == "mingw" ]; then + cmake -S "$SRCDIR" -B build -DMODELICA_UTILITIES_INCLUDE_DIR="$TESTDIR" -DMODELICA_DEBUG_TIME_EVENTS=ON -DCMAKE_BUILD_TYPE=${{ matrix.configuration }} -G "MinGW Makefiles" + else + cmake -S "$SRCDIR" -B build -DMODELICA_UTILITIES_INCLUDE_DIR="$TESTDIR" -DMODELICA_DEBUG_TIME_EVENTS=ON -DCMAKE_BUILD_TYPE=${{ matrix.configuration }} -DCMAKE_C_FLAGS="-std=c89 -Wall -Wextra" + fi + env: + SRCDIR: ${{ github.workspace }}/Modelica/Resources/BuildProjects/CMake + TESTDIR: ${{ github.workspace }}/.CI/Test + - name: Build with ${{ matrix.compiler }} + run: | + if [ "${{ matrix.compiler }}" == "msvc" ]; then + cmake --build build --config ${{ matrix.configuration }} + else + cmake --build build -- -j8 + fi + + html_documentation_checks: + timeout-minutes: 5 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 5 + - name: Setup python environment + uses: actions/setup-python@v5 + with: + python-version: 3.8 + - name: Install python packages + run: | + pip install --disable-pip-version-check --user pytidylib + pip install --disable-pip-version-check --user futures + - name: Build html tidy + run: | + git clone --branch 5.8.0 --depth=1 https://github.com/htacg/tidy-html5.git + pushd tidy-html5 + cmake . + make + sudo make install + popd + sudo ldconfig + - name: Tidy html + run: | + echo "::add-matcher::./.github/tidyHTML.json" + python ./.CI/check_html.py tidyHTML ./ + echo "::remove-matcher owner=tidyHTML::" + - name: Check tags + run: | + echo "::add-matcher::./.github/checkTags.json" + python ./.CI/check_html.py checkTags ./ + echo "::remove-matcher owner=checkTags::" + + syntax_checks: + timeout-minutes: 5 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 5 + - name: Get moparser + run: git clone --depth=1 https://github.com/modelica-tools/ModelicaSyntaxChecker + - name: Check file encoding + run: "! find . -name '*.mo' -exec bash -c 'iconv -o /dev/null -f utf8 -t utf8 \"{}\" |& sed \"s,^,{}: ,\"' ';' | grep '.'" + - name: Check for UTF-8 BOM + run: "! find . -name '*.mo' -print0 | xargs -0 grep -l $'^\\xEF\\xBB\\xBF' | grep ." + - name: Check syntax + run: | + echo "::add-matcher::./.github/moparser.json" + ModelicaSyntaxChecker/Linux64/moparser -v 3.4 -r ModelicaTableAdditions + echo "::remove-matcher owner=moparser::" + + deprecation_checks: + timeout-minutes: 5 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 5 + - name: Setup python environment + uses: actions/setup-python@v5 + with: + python-version: 3.8 + - name: Check deprecated Text.lineColor annotation + run: | + echo "::add-matcher::./.github/check_deprecated_line_color.json" + python ./.CI/check_deprecated_line_color.py ./ + echo "::remove-matcher owner=check_deprecated_line_color::" diff --git a/README.md b/README.md index cd6bdeb..cf7e46f 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Free Modelica library for univariate and bivariate interpolation and extrapolati ## Status -[![GitHub license](https://img.shields.io/github/license/tbeu/ModelicaTableAdditions)](https://github.com/tbeu/ModelicaTableAdditions/blob/main/LICENSE) +[![CI checks](https://github.com/tbeu/ModelicaTableAdditions/actions/workflows/checkCI.yml/badge.svg)](https://github.com/tbeu/ModelicaTableAdditions/actions/workflows/checkCI.yml) [![GitHub license](https://img.shields.io/github/license/tbeu/ModelicaTableAdditions)](https://github.com/tbeu/ModelicaTableAdditions/blob/main/LICENSE) ## Library description