Skip to content

Commit

Permalink
Add a license header scanner
Browse files Browse the repository at this point in the history
Simple scanner to check code files for a license header. Does not care
about the exact formatting of the license header as long as all the text
exists in the correct order.

This version only supports headers using '#' as the comment string.

Issue: RELENG-279
Change-Id: Id4030f040c3de4350c59776ed21eed497e5d6f8d
Signed-off-by: Thanh Ha <[email protected]>
  • Loading branch information
zxiiro committed Jul 28, 2017
1 parent 6109492 commit 8f0d84c
Show file tree
Hide file tree
Showing 11 changed files with 249 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/commands/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ bash. It supports the following commands.
:maxdepth: 2

deploy
license
nexus
openstack
sign
Expand Down
16 changes: 16 additions & 0 deletions docs/commands/license.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
*******
License
*******

.. program-output:: lftools license --help

Commands
========

.. contents:: License Commands
:local:

check
-----

.. program-output:: lftools license check --help
2 changes: 2 additions & 0 deletions lftools/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

from lftools.cli.deploy import deploy
from lftools.cli.jenkins import jenkins_cli
from lftools.cli.license import license
from lftools.cli.nexus import nexus
from lftools.cli.sign import sign
from lftools.cli.version import version
Expand All @@ -32,6 +33,7 @@ def cli(ctx):

cli.add_command(deploy)
cli.add_command(jenkins_cli, name='jenkins')
cli.add_command(license)
cli.add_command(nexus)
cli.add_command(openstack)
cli.add_command(sign)
Expand Down
66 changes: 66 additions & 0 deletions lftools/cli/license.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# SPDX-License-Identifier: EPL-1.0
##############################################################################
# Copyright (c) 2017 The Linux Foundation and others.
#
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Eclipse Public License v1.0
# which accompanies this distribution, and is available at
# http://www.eclipse.org/legal/epl-v10.html
##############################################################################
"""Scan code for license headers."""

__author__ = 'Thanh Ha'


import sys

import click

from lftools.license import check_license
from lftools.license import check_license_directory


@click.group()
@click.pass_context
def license(ctx):
"""Scan code for license headers."""
pass


@click.command()
@click.argument('source')
@click.option('-l', '--license', default='license-header.txt',
help='License header file to compare against.')
@click.pass_context
def check(ctx, license, source):
"""Check files for missing license headers.
Does not care if about line formatting of the license as long as all of the
text is there and in the correct order.
Note: This code only supports '#' comments for license headers.
"""
exit_code = check_license(license, source)
sys.exit(exit_code)


@click.command(name='check-dir')
@click.argument('directory')
@click.option('-e', '--extension', default='py',
help='File extension to search for.')
@click.option('-l', '--license', default='license-header.txt',
help='License header file to compare against.')
@click.pass_context
def check_directory(ctx, license, directory, extension):
"""Check directory for files missing license headers.
Does not care if about line formatting of the license as long as all of the
text is there and in the correct order.
Note: This code only supports '#' comments for license headers.
"""
check_license_directory(license, directory, extension)


license.add_command(check)
license.add_command(check_directory)
74 changes: 74 additions & 0 deletions lftools/license.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# -*- coding: utf-8 -*-
# SPDX-License-Identifier: EPL-1.0
##############################################################################
# Copyright (c) 2017 The Linux Foundation and others.
#
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Eclipse Public License v1.0
# which accompanies this distribution, and is available at
# http://www.eclipse.org/legal/epl-v10.html
##############################################################################
"""Scans code for a valid license header."""

__author__ = 'Thanh Ha'


import os
import re
import sys


def get_header_text(_file):
"""Scan a file and pulls out the license header.
Returns a string containing the license header with newlines and copyright
lines stripped.
Note: This function only supports '#' comments for license headers.
"""
text = ''
with open(_file, 'r') as data:
lines = data.readlines()
for line in lines:
result = re.search(r'\s*[#]', line)
if not result:
break
string = re.sub(r'^\s*#+', '', line).strip()
if bool(re.match('Copyright', string, re.I)): # Ignore the Copyright line
continue
text += ' {}'.format(string)
# Strip unnecessary spacing
text = re.sub('\s+', ' ', text).strip()
return text


def check_license(license_file, code_file):
"""Compare a file with the provided license header.
Reports if license header is missing or does not match the text of
license_file.
"""
license_header = get_header_text(license_file)
code_header = get_header_text(code_file)

if not license_header in code_header:
print('ERROR: {} is missing or has incorrect license header.'.format(code_file))
return 1

return 0


def check_license_directory(license_file, directory, extension="py"):
"""Search a directory for files and calls check_license()."""
missing_license = False

for root, dirs, files in os.walk(directory):
for file in files:
if file.endswith(".{}".format(extension)):
if check_license(license_file, os.path.join(root, file)):
missing_license = True

if missing_license:
sys.exit(1)

print('Scan completed did not detect any files missing license headers.')
9 changes: 9 additions & 0 deletions license-header.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# SPDX-License-Identifier: EPL-1.0
##############################################################################
# COPYRIGHT
#
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Eclipse Public License v1.0
# which accompanies this distribution, and is available at
# http://www.eclipse.org/legal/epl-v10.html
##############################################################################
9 changes: 9 additions & 0 deletions tests/fixtures/license/license-header.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# SPDX-License-Identifier: EPL-1.0
##############################################################################
# COPYRIGHT
#
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Eclipse Public License v1.0
# which accompanies this distribution, and is available at
# http://www.eclipse.org/legal/epl-v10.html
##############################################################################
10 changes: 10 additions & 0 deletions tests/fixtures/license/license.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# SPDX-License-Identifier: EPL-1.0
##############################################################################
# Copyright (c) 2017 The Linux Foundation and others.
#
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Eclipse Public License v1.0
# which accompanies this distribution, and is available at
# http://www.eclipse.org/legal/epl-v10.html
##############################################################################
"""Test code file with license header."""
1 change: 1 addition & 0 deletions tests/fixtures/license/no_license1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Test code file without license header."""
1 change: 1 addition & 0 deletions tests/fixtures/license/no_license2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Test code file without license header."""
60 changes: 60 additions & 0 deletions tests/test_license.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# SPDX-License-Identifier: EPL-1.0
##############################################################################
# Copyright (c) 2017 The Linux Foundation and others.
#
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Eclipse Public License v1.0
# which accompanies this distribution, and is available at
# http://www.eclipse.org/legal/epl-v10.html
##############################################################################
"""Test license command."""

import os

import pytest

from lftools import cli

FIXTURE_DIR = os.path.join(
os.path.dirname(os.path.realpath(__file__)),
'fixtures',
)


@pytest.mark.datafiles(
os.path.join(FIXTURE_DIR, 'license'),
)
def test_check_license(cli_runner, datafiles):
"""Test check_license() command."""
os.chdir(str(datafiles))

# Check that license checker passes when file has license.
result = cli_runner.invoke(cli.cli, ['license', 'check', 'license.py'])
# noqa: B101 .
assert result.exit_code == 0

# Check that license checker fails when file is missing license.
result = cli_runner.invoke(cli.cli, ['license', 'check', 'no_license1.py'])
# noqa: B101 .
assert result.exit_code == 1


@pytest.mark.datafiles(
os.path.join(FIXTURE_DIR, 'license'),
)
def test_check_license_directory(cli_runner, datafiles):
"""Test check_license_directory() command."""
os.chdir(str(datafiles))

# Check that check-dir fails due to directory containing files
# with no license.
result = cli_runner.invoke(cli.cli, ['license', 'check-dir', '.'])
# noqa: B101 .
assert result.exit_code == 1

# Check that check-dir passes when directory contains files with licenses
os.remove('no_license1.py')
os.remove('no_license2.py')
result = cli_runner.invoke(cli.cli, ['license', 'check-dir', '.'])
# noqa: B101 .
assert result.exit_code == 0

0 comments on commit 8f0d84c

Please sign in to comment.