Commit e9f68a15 authored by Ben Gamari's avatar Ben Gamari 🐢

gitlab-ci: Add linters

These are taken from our previous arcanist linters as well as the
gitolite hooks but with some heavy refactoring.
parent 9b8713e8
FROM debian:stretch
ENV LANG C.UTF-8
RUN apt-get update -qq; apt-get install -qy gnupg sudo git python3
RUN echo 'deb http://ppa.launchpad.net/hvr/ghc/ubuntu xenial main' > /etc/apt/sources.list.d/ghc.list
RUN apt-key adv --keyserver keyserver.ubuntu.com --recv-keys F6F88286
RUN apt-get update -qq
# Basic Haskell toolchain
RUN apt-get install -qy cabal-install-2.2 ghc-8.4.2
ENV PATH /home/ghc/.local/bin:/opt/cabal/2.2/bin:/opt/ghc/8.4.2/bin:$PATH
# Create a normal user.
RUN adduser ghc --gecos "GHC builds" --disabled-password
RUN echo "ghc ALL = NOPASSWD : ALL" > /etc/sudoers.d/ghc
USER ghc
WORKDIR /home/ghc/
# Build Linting tools
RUN cabal update
RUN git clone git://github.com/haskell-infra/git-haskell-org-hooks && \
cd git-haskell-org-hooks && \
cabal install
ENV PATH /home/ghc/.cabal/bin:$PATH
CMD ["bash"]
......@@ -7,11 +7,34 @@ before_script:
- git submodule update --init --recursive
- git checkout .gitmodules
stages:
- lint
- build
############################################################
# Linting
############################################################
ghc-linters:
stage: lint
image: ghcci/linters:0.1
script:
- |
if [ -n "$CI_MERGE_REQUEST_ID" ]; then
base="$(git merge-base $CI_MERGE_REQUEST_BRANCH_NAME HEAD)"
validate-commit-msg .git $(git rev-list $base..$CI_COMMIT_SHA)
submodchecker .git $(git rev-list $base..$CI_COMMIT_SHA)
validate-whitespace .git $(git rev-list $base..$CI_COMMIT_SHA)
.gitlab/linters/check-makefiles.py $base $CI_COMMIT_SHA
.gitlab/linters/check-cpp.py $base $CI_COMMIT_SHA
fi
############################################################
# Validation via Pipelines (hadrian)
############################################################
.validate-hadrian:
stage: build
allow_failure: true
script:
- bash .circleci/prepare-system.sh
......@@ -38,7 +61,7 @@ validate-x86_64-linux-deb8-hadrian:
############################################################
.validate:
allow_failure: true
stage: build
script:
- make clean || true
- ./boot
......@@ -61,6 +84,7 @@ validate-x86_64-linux-deb8-hadrian:
validate-x86_64-darwin:
extends: .validate
allow_failure: true
tags:
- x86_64-darwin
variables:
......@@ -120,6 +144,7 @@ validate-x86_64-linux-deb9:
validate-x86_64-linux-deb9-llvm:
extends: .validate-linux
allow_failure: true
image: ghcci/x86_64-linux-deb9:0.2
variables:
BUILD_FLAVOUR: perf-llvm
......@@ -144,6 +169,7 @@ validate-x86_64-linux-fedora27:
validate-x86_64-linux-deb9-integer-simple:
extends: .validate-linux
allow_failure: true
variables:
INTEGER_LIBRARY: integer-simple
image: ghcci/x86_64-linux-deb9:0.2
......@@ -163,6 +189,7 @@ validate-x86_64-linux-deb9-unreg:
############################################################
.validate-x86_64-windows:
stage: build
variables:
GHC_VERSION: "8.6.2"
script:
......@@ -193,6 +220,7 @@ validate-x86_64-linux-deb9-unreg:
############################################################
.circleci:
stage: build
image: ghcci/x86_64-linux-deb8:0.1
artifacts:
when: always
......
#!/usr/bin/env python3
# A linter to warn for ASSERT macros which are separated from their argument
# list by a space, which Clang's CPP barfs on
from linter import run_linters, RegexpLinter
linters = [
RegexpLinter(r'ASSERT\s+\(',
message='CPP macros should not have a space between the macro name and their argument list'),
RegexpLinter(r'ASSERT2\s+\(',
message='CPP macros should not have a space between the macro name and their argument list'),
RegexpLinter(r'#ifdef\s+',
message='`#if defined(x)` is preferred to `#ifdef x`'),
RegexpLinter(r'#if\s+defined\s+',
message='`#if defined(x)` is preferred to `#if defined x`'),
RegexpLinter(r'#ifndef\s+',
message='`#if !defined(x)` is preferred to `#ifndef x`'),
]
if __name__ == '__main__':
run_linters(linters)
#!/usr/bin/env python3
"""
Warn for use of `--interactive` inside Makefiles (#11468).
Encourage the use of `$(TEST_HC_OPTS_INTERACTIVE)` instead of
`$(TEST_HC_OPTS) --interactive -ignore-dot-ghci -v0`. It's too easy to
forget one of those flags when adding a new test.
"""
from linter import run_linters, RegexpLinter
linters = [
RegexpLinter(r'--interactive',
message = "Warning: Use `$(TEST_HC_OPTS_INTERACTIVE)` instead of `--interactive -ignore-dot-ghci -v0`.")
]
if __name__ == '__main__':
run_linters(linters) #$, subdir='testsuite')
"""
Utilities for linters
"""
import os
import sys
import re
import textwrap
import subprocess
from typing import List, Optional
from collections import namedtuple
def lint_failure(file, line_no, line_content, message):
""" Print a lint failure message. """
wrapper = textwrap.TextWrapper(initial_indent=' ',
subsequent_indent=' ')
body = wrapper.fill(message)
msg = '''
{file}:
|
{line_no:5d} | {line_content}
|
{body}
'''.format(file=file, line_no=line_no,
line_content=line_content,
body=body)
print(textwrap.dedent(msg))
def get_changed_files(base_commit, head_commit,
subdir: str = '.'):
""" Get the files changed by the given range of commits. """
cmd = ['git', 'diff', '--name-only',
base_commit, head_commit, '--', subdir]
files = subprocess.check_output(cmd)
return files.decode('UTF-8').split('\n')
Warning = namedtuple('Warning', 'path,line_no,line_content,message')
class Linter(object):
"""
A :class:`Linter` must implement :func:`lint`, which looks at the
given path and calls :func:`add_warning` for any lint issues found.
"""
def __init__(self):
self.warnings = [] # type: List[Warning]
def add_warning(self, w: Warning):
self.warnings.append(w)
def lint(self, path):
pass
class LineLinter(Linter):
"""
A :class:`LineLinter` must implement :func:`lint_line`, which looks at
the given line from a file and calls :func:`add_warning` for any lint
issues found.
"""
def lint(self, path):
if os.path.isfile(path):
with open(path, 'r') as f:
for line_no, line in enumerate(f):
self.lint_line(path, line_no+1, line)
def lint_line(self, path, line_no, line):
pass
class RegexpLinter(LineLinter):
"""
A :class:`RegexpLinter` produces the given warning message for
all lines matching the given regular expression.
"""
def __init__(self, regex, message):
LineLinter.__init__(self)
self.re = re.compile(regex)
self.message = message
def lint_line(self, path, line_no, line):
if self.re.search(line):
w = Warning(path=path, line_no=line_no, line_content=line[:-1],
message=self.message)
self.add_warning(w)
def run_linters(linters: List[Linter],
subdir: str = '.') -> None:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('base', help='Base commit')
parser.add_argument('head', help='Head commit')
args = parser.parse_args()
for path in get_changed_files(args.base, args.head, subdir):
if path.startswith('.gitlab/linters'):
continue
for linter in linters:
linter.lint(path)
warnings = [warning
for linter in linters
for warning in linter.warnings]
warnings = sorted(warnings, key=lambda x: (x.path, x.line_no))
for w in warnings:
lint_failure(w.path, w.line_no, w.line_content, w.message)
if len(warnings) > 0:
sys.exit(1)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment