Commit e67de75f authored by Rusty Russell's avatar Rusty Russell

ccanlint: add simple check for comment referring to LICENSE file.

After discussion with various developers (particularly the Samba
team), there's a consensus that a reference to the license in each
source file is useful.  Since CCAN modules are designed to be cut and
paste, this helps avoid any confusion should the LICENSE file go
missing.

We also detect standard boilerplates, in which case a one-line summary
isn't necessary.
parent 582d7629
......@@ -4,6 +4,7 @@ TEST_OBJS := $(NORMAL_TEST_CFILES:.c=.o) $(COMPULSORY_TEST_CFILES:.c=.o)
CORE_OBJS := tools/ccanlint/ccanlint.o \
tools/ccanlint/file_analysis.o \
tools/ccanlint/licenses.o \
tools/doc_extract-core.o \
tools/depends.o \
tools/tools.o \
......
......@@ -4,6 +4,7 @@
#include <ccan/list/list.h>
#include <stdbool.h>
#include "../doc_extract.h"
#include "licenses.h"
#define REGISTER_TEST(name, ...) extern struct ccanlint name
......@@ -16,21 +17,6 @@
4 == Describe every action. */
extern int verbose;
enum license {
LICENSE_LGPLv2_PLUS,
LICENSE_LGPLv2,
LICENSE_LGPLv3,
LICENSE_LGPL,
LICENSE_GPLv2_PLUS,
LICENSE_GPLv2,
LICENSE_GPLv3,
LICENSE_GPL,
LICENSE_BSD,
LICENSE_MIT,
LICENSE_PUBLIC_DOMAIN,
LICENSE_UNKNOWN
};
struct manifest {
char *dir;
/* The module name, ie. final element of dir name */
......@@ -195,6 +181,9 @@ struct ccan_file {
/* Leak output from valgrind. */
char *leak_info;
/* Simplified stream (lowercase letters and single spaces) */
char *simplified;
};
/* A new ccan_file, with the given name (talloc_steal onto returned value). */
......@@ -209,6 +198,9 @@ char **get_ccan_file_lines(struct ccan_file *f);
/* Use this rather than accessing f->lines directly: loads on demand. */
struct line_info *get_ccan_line_info(struct ccan_file *f);
/* Use this rather than accessing f->simplified directly: loads on demand. */
const char *get_ccan_simplified(struct ccan_file *f);
enum line_compiled {
NOT_COMPILED,
COMPILED,
......
......@@ -86,6 +86,7 @@ struct ccan_file *new_ccan_file(const void *ctx, const char *dir, char *name)
f->fullname = talloc_asprintf(f, "%s/%s", dir, f->name);
f->contents = NULL;
f->cov_compiled = NULL;
f->simplified = NULL;
return f;
}
......
#include "licenses.h"
#include "ccanlint.h"
#include <ccan/talloc/talloc.h>
#include <ccan/str/str.h>
const struct license_info licenses[] = {
{ "LGPLv2+", "LGPL",
{ "gnu lesser general public license",
"version 2",
"or at your option any later version"
}
},
{ "LGPLv2", "LGPL",
{ "gnu lesser general public license",
"version 2",
NULL
}
},
{ "LGPLv3", "LGPL",
{ "gnu lesser general public license",
"version 3",
NULL
}
},
{ "LGPL", "LGPL",
{ "gnu lesser general public license",
NULL,
NULL
}
},
{ "GPLv2+", "GPL",
{ "gnu general public license",
"version 2",
"or at your option any later version"
}
},
{ "GPLv2", "GPL",
{ "gnu general public license",
"version 2",
NULL
}
},
{ "GPLv3", "GPL",
{ "gnu general public license",
"version 3",
NULL
}
},
{ "GPL", "GPL",
{ "gnu general public license",
NULL,
NULL
}
},
{ "BSD-3CLAUSE", "BSD",
{ "redistributions of source code must retain",
"redistributions in binary form must reproduce",
"endorse or promote"
}
},
{ "BSD-MIT", "MIT",
{ "without restriction",
"above copyright notice",
"without warranty"
}
},
{ "Public domain", "Public domain",
{ NULL, NULL, NULL }
},
{ "Unknown license", "Unknown license",
{ NULL, NULL, NULL }
},
};
const char *get_ccan_simplified(struct ccan_file *f)
{
if (!f->simplified) {
unsigned int i, j;
/* Simplify for easy matching: only alnum and single spaces. */
f->simplified = talloc_strdup(f, get_ccan_file_contents(f));
for (i = 0, j = 0; f->simplified[i]; i++) {
if (cisupper(f->simplified[i]))
f->simplified[j++] = tolower(f->simplified[i]);
else if (cislower(f->simplified[i]))
f->simplified[j++] = f->simplified[i];
else if (cisdigit(f->simplified[i]))
f->simplified[j++] = f->simplified[i];
else if (cisspace(f->simplified[i])) {
if (j != 0 && f->simplified[j-1] != ' ')
f->simplified[j++] = ' ';
}
}
f->simplified[j] = '\0';
}
return f->simplified;
}
bool find_boilerplate(struct ccan_file *f, enum license license)
{
unsigned int i;
for (i = 0; i < NUM_CLAUSES; i++) {
if (!licenses[license].clause[i])
break;
if (!strstr(get_ccan_simplified(f),
licenses[license].clause[i])) {
return false;
}
}
return true;
}
#ifndef CCANLINT_LICENSES_H
#define CCANLINT_LICENSES_H
#include <stdbool.h>
enum license {
LICENSE_LGPLv2_PLUS,
LICENSE_LGPLv2,
LICENSE_LGPLv3,
LICENSE_LGPL,
LICENSE_GPLv2_PLUS,
LICENSE_GPLv2,
LICENSE_GPLv3,
LICENSE_GPL,
LICENSE_BSD,
LICENSE_MIT,
LICENSE_PUBLIC_DOMAIN,
LICENSE_UNKNOWN
};
#define NUM_CLAUSES 3
struct license_info {
const char *name;
const char *shortname;
/* Edit distance is expensive, and this works quite well. */
const char *clause[NUM_CLAUSES];
};
extern const struct license_info licenses[];
struct ccan_file;
bool find_boilerplate(struct ccan_file *f, enum license license);
#endif /* CCANLINT_LICENSES_H */
#include <tools/ccanlint/ccanlint.h>
#include <ccan/foreach/foreach.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <limits.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <err.h>
#include <ccan/talloc/talloc.h>
#include <ccan/str/str.h>
#include <ccan/str_talloc/str_talloc.h>
static void check_license_comment(struct manifest *m,
bool keep,
unsigned int *timeleft, struct score *score)
{
struct list_head *list;
/* No requirements on public domain. */
if (m->license == LICENSE_PUBLIC_DOMAIN
|| m->license == LICENSE_UNKNOWN) {
score->pass = true;
score->score = score->total;
return;
}
foreach_ptr(list, &m->c_files, &m->h_files) {
struct ccan_file *f;
list_for_each(list, f, list) {
unsigned int i;
char **lines = get_ccan_file_lines(f);
struct line_info *info = get_ccan_line_info(f);
bool found_license = false, found_flavor = false;
for (i = 0; lines[i]; i++) {
if (info[i].type == CODE_LINE)
break;
if (strstr(lines[i], "LICENSE"))
found_license = true;
if (strstr(lines[i],
licenses[m->license].shortname))
found_flavor = true;
}
if ((!found_license || !found_flavor)
&& !find_boilerplate(f, m->license)) {
score_file_error(score, f, lines[i] ? i : 0,
"No reference to license"
" found");
}
}
}
if (list_empty(&score->per_file_errors)) {
score->pass = true;
score->score = score->total;
}
}
struct ccanlint license_comment = {
.key = "license_comment",
.name = "Source and header files refer to LICENSE",
.check = check_license_comment,
.needs = "license_exists"
};
REGISTER_TEST(license_comment);
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