Skip to content

Commit

Permalink
Add 'cython-lint' to the linter workflows. (#49)
Browse files Browse the repository at this point in the history
  • Loading branch information
bbassett-tibco committed Jan 12, 2024
1 parent 11a90a2 commit 252539a
Show file tree
Hide file tree
Showing 14 changed files with 212 additions and 145 deletions.
111 changes: 77 additions & 34 deletions .github/scripts/run_pylint_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,62 +6,105 @@
import sys

from github import Github
import pylint.lint
from pylint import lint as pl_run
from pylint.__pkginfo__ import __version__ as pl_version


def run_pylint(directory):
saved_stdout = sys.stdout
sys.stdout = io.StringIO()
result = pylint.lint.Run([directory], exit=False)
output = sys.stdout.getvalue()
saved_stdout.write(output)
sys.stdout.close()
sys.stdout = saved_stdout
return output, result.linter.msg_status
from cython_lint import cython_lint as cl_run
from cython_lint import __version__ as cl_version


def main():
# Process command line arguments
parser = argparse.ArgumentParser(
"Check if any opened issues have been closed, run pylint, and open an issue if pylint complains")
"Check if any opened issues have been closed, run linters, and open an issue if any complain")
parser.add_argument("--token", help="The GitHub API token to use")
parser.add_argument("--repo", help="The owner and repository we are operating on")
parser.add_argument("--label", help="The name of the GitHub label for generated issues")
parser.add_argument("--src", help="The source directory to run pylint on")
parser.add_argument("--label-pylint", help="The name of the GitHub label for 'pylint' generated issues")
parser.add_argument("--label-cython", help="The name of the GitHub label for 'cython-lint' generated issues")
args = parser.parse_args()
# Connect to GitHub REST API
gh = Github(args.token)
# Determine if we should run pylint
open_issues = gh.search_issues(f"repo:{args.repo} label:{args.label} is:issue is:open")
# Run the linters
pylint(gh, args.repo, args.label_pylint)
cython_lint(gh, args.repo, args.label_cython)


def _check_issues(gh, repo, tool, label):
open_issues = gh.search_issues(f"repo:{repo} label:{label} is:issue is:open")
if open_issues.totalCount != 0:
print(f"Skipping pylint run due to existing issue {open_issues[0].html_url}.")
sys.exit(0)
# Now run pylint
(output, pylint_exitcode) = run_pylint(args.src)
if pylint_exitcode == 0:
sys.exit(0)
# File an issue
issue_title = f"New version of pylint ({pl_version}) identifies new issues"
issue_body = (f"A version of `pylint` is available in the Python package repositories that identifies issues "
f"with the `spotfire` package. Since we attempt to keep all pylint issues out of the source "
print(f"Skipping '{tool}' run due to existing issue {open_issues[0].html_url}.")
return True
else:
return False


def _file_issue(gh, repo, tool, tool_args, tool_version, label, output):
issue_title = f"New version of pylint ({tool_version}) identifies new issues"
issue_body = (f"A version of `{tool}` is available in the Python package repositories that identifies issues "
f"with the `spotfire` package. Since we attempt to keep all lint issues out of the source "
f"code (either by fixing the issue identified or by disabling that message with a localized "
f"comment), this is indicative of a new check in this new version of `pylint`.\n\n"
f"comment), this is indicative of a new check in this new version of `{tool}`.\n\n"
f"Please investigate these issues, and either fix the source or disable the check with a "
f"comment. Further checks by this automation will be held until this issue is closed. Make "
f"sure that the fix updates the `pylint` requirement in `requirements_lint.txt` to the version "
f"identified here ({pl_version}).\n\n"
f"For reference, here is the output of this version of `pylint`:\n\n"
f"sure that the fix updates the `{tool}` requirement in `requirements_lint.txt` to the version "
f"identified here ({tool_version}).\n\n"
f"For reference, here is the output of this version of `{tool}`:\n\n"
f"```\n"
f"$ pylint {args.src}\n"
f"$ {tool} {tool_args}\n"
f"{output}\n"
f"```\n\n"
f"*This issue was automatically opened by the `pylint.yaml` workflow.*\n")
repo = gh.get_repo(args.repo)
repo_label = repo.get_label(args.label)
repo = gh.get_repo(repo)
repo_label = repo.get_label(label)
new_issue = repo.create_issue(title=issue_title, body=issue_body, labels=[repo_label])
print(f"Opened issue {new_issue.html_url}")


class _StdoutCapture:
def __init__(self):
self._saved = None
self._capture = io.StringIO()

def __enter__(self):
self._saved = sys.stdout
sys.stdout = self._capture

def __exit__(self, exc_type, exc_val, exc_tb):
sys.stdout = self._saved
self._saved = None

def output(self):
return self._capture.getvalue()


def pylint(gh, repo, label):
# Determine if we should run pylint
if _check_issues(gh, repo, "pylint", label):
return

# Now run pylint
with _StdoutCapture() as capture:
result = pl_run.Run(["spotfire"], exit=False)
if result.linter.msg_status == 0:
return

# File an issue
_file_issue(gh, repo, "pylint", "spotfire", pl_version, label, capture.output())


def cython_lint(gh, repo, label):
# Determine if we should run cython-lint
if _check_issues(gh, repo, "cython-lint", label):
return

# Now run cython-lint
with _StdoutCapture() as capture:
result = cl_run.main(["spotfire", "vendor"])
if result == 0:
return

# File an issue
_file_issue(gh, repo, "cython-lint", "spotfire vendor", cl_version, label, capture.output())


if __name__ == "__main__":
main()
9 changes: 7 additions & 2 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ jobs:
- uses: actions/upload-artifact@v3
with:
name: analysis-files
path: 'pyproject.toml'
path: |
pyproject.toml
**/*.pyx
**/*.pxd
**/*.pxi
- name: Dynamic Elements
id: dynamic
run: |
Expand Down Expand Up @@ -156,7 +160,8 @@ jobs:
- name: Install Tools
run: |
pip install `ls dist/*.whl`[lint]
- name: Static Analysis with pylint
- name: Run Analysis Tools
run: |
cd analysis
pylint spotfire
cython-lint spotfire vendor
12 changes: 6 additions & 6 deletions .github/workflows/pylint.yaml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
name: Check Current PyLint
name: Check Current Linters
on:
schedule:
- cron: '17 17 * * 1'
jobs:
pylint:
name: Check PyLint
lint:
name: Check Linters
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
Expand All @@ -14,7 +14,7 @@ jobs:
python-version: '3.x'
- name: Install Tools
run: |
pip install PyGithub pylint
- name: Run PyLint
pip install PyGithub pylint cython-lint
- name: Run Analysis Tools
run: |
python .github/scripts/run_pylint_check.py --token ${{ secrets.GITHUB_TOKEN }} --repo ${{ github.repository }} --label automated/pylint --src spotfire
python .github/scripts/run_pylint_check.py --token ${{ secrets.GITHUB_TOKEN }} --repo ${{ github.repository }} --label-pylint automated/pylint --label-cython automated/cython-lint
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ dev = [
# Static analysis requirements
lint = [
"pylint == 3.0.3",
"cython-lint == 0.16.0",
]

[tool.pylint.main]
Expand Down Expand Up @@ -262,3 +263,6 @@ dummy-variables-rgx = "_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unuse
ignored-argument-names = "_.*|^ignored_|^unused_"
# init-import =
redefining-builtins-modules = ["six.moves", "future.builtins"]

[tool.cython-lint]
max-line-length = 120
1 change: 1 addition & 0 deletions spotfire/cabfile.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

"""Tools to create Microsoft cabinet files. Only defined on Windows platforms."""


class CabFile:
"""Class with methods to open, write, and close Microsoft cabinet files."""
def __init__(self, file: str) -> None:
Expand Down
2 changes: 1 addition & 1 deletion spotfire/cabfile_helpers.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# in the license file that is distributed with this file.

cdef extern from "cabfile_helpers.h":
### FCI callback functions
# FCI callback functions
fci.PFNFCIALLOC _fci_cb_alloc
fci.PFNFCIFREE _fci_cb_free
fci.PFNFCIOPEN _fci_cb_open
Expand Down
1 change: 1 addition & 0 deletions spotfire/cabfile_windows.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ from vendor.windows cimport fci

include "cabfile_helpers.pxi"


@cython.auto_pickle(False)
cdef class CabFile:
"""Class with methods to open, write, and close Microsoft cabinet files."""
Expand Down
2 changes: 2 additions & 0 deletions spotfire/codesign.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ cpdef enum CertificateStoreLocation:
CURRENT_USER = 1
LOCAL_MACHINE = 2


def codesign_file(filename, certificate, password, timestamp = None, use_rfc3161 = False, use_sha256 = False):
"""Codesign a file with the Microsoft signing API found in mssign32.dll using a certificate found in a PFX file
or PKCS#12 container.
Expand All @@ -26,6 +27,7 @@ def codesign_file(filename, certificate, password, timestamp = None, use_rfc3161
"""
raise OSError("Codesigning not supported on non-Win32 platforms")


def codesign_file_from_store(filename, store_location, store_name, store_cn, timestamp = None, use_rfc3161 = False,
use_sha256 = False):
"""Codesign a file with the Microsoft signing API found in mssign32.dll using a certificate found in a system
Expand Down
16 changes: 10 additions & 6 deletions spotfire/codesign_windows.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,9 @@ cpdef void codesign_file_from_store(filename,

cdef windows.LPCWSTR _object_to_wstr(object obj):
"""Convert a Python object into a Windows wide string.
:param obj: Python object to convert
:return: wide string buffer containing the converted string. The caller is responsible for cleaning up this
:return: wide string buffer containing the converted string. The caller is responsible for cleaning up this
buffer using the native ``free`` function (from the Cython ``libc.stdlib`` module).
"""
cdef int len_
Expand Down Expand Up @@ -207,13 +207,16 @@ cdef void _codesign_file_core(filename,
signer_sign_ex_fun = <mssign32.SignerSignExType>windows.GetProcAddress(mssign32_library, "SignerSignEx")
if signer_sign_ex_fun is NULL:
raise CodesignError("Cannot find function 'SignerSignEx'")
signer_time_stamp_fun = <mssign32.SignerTimeStampType>windows.GetProcAddress(mssign32_library, "SignerTimeStamp")
signer_time_stamp_fun = <mssign32.SignerTimeStampType>(
windows.GetProcAddress(mssign32_library, "SignerTimeStamp"))
if signer_time_stamp_fun is NULL:
raise CodesignError("Cannot find function 'SignerTimeStamp'")
signer_time_stamp_ex2_fun = <mssign32.SignerTimeStampEx2Type>windows.GetProcAddress(mssign32_library, "SignerTimeStampEx2")
signer_time_stamp_ex2_fun = <mssign32.SignerTimeStampEx2Type>(
windows.GetProcAddress(mssign32_library, "SignerTimeStampEx2"))
if signer_time_stamp_ex2_fun is NULL:
raise CodesignError("Cannot find function 'SignerTimeStampEx2'")
signer_free_signer_context_fun = <mssign32.SignerFreeSignerContextType>windows.GetProcAddress(mssign32_library, "SignerFreeSignerContext")
signer_free_signer_context_fun = <mssign32.SignerFreeSignerContextType>(
windows.GetProcAddress(mssign32_library, "SignerFreeSignerContext"))
if signer_free_signer_context_fun is NULL:
raise CodesignError("Cannot find function 'SignerFreeSignerContext'")

Expand All @@ -233,7 +236,8 @@ cdef void _codesign_file_core(filename,
found_private_key = True
else:
cert_context = wincrypt.CertFindCertificateInStore(cert_store,
wincrypt.X509_ASN_ENCODING | wincrypt.PKCS_7_ASN_ENCODING,
wincrypt.X509_ASN_ENCODING |
wincrypt.PKCS_7_ASN_ENCODING,
0, cert_find_type, cert_find_param, cert_context)
if cert_context is NULL:
raise CodesignError("Could not get certificate from store")
Expand Down
Loading

0 comments on commit 252539a

Please sign in to comment.