Test / test (push) Successful in 7m5s
Release / gates (push) Successful in 7m28s
Release / build (amd64, freebsd) (push) Successful in 2m52s
Release / build (amd64, linux) (push) Successful in 2m46s
Release / build (arm64, freebsd) (push) Successful in 2m22s
Release / build (arm64, linux) (push) Successful in 2m38s
Release / build (loong64, linux) (push) Successful in 2m7s
Release / build (riscv64, linux) (push) Successful in 2m17s
Release / release (push) Successful in 1m0s
Assisted-by: GLM 5.3
225 lines
8.7 KiB
Perl
225 lines
8.7 KiB
Perl
#!/usr/bin/env perl
|
|
# notices.pl writes NOTICE.md in the repository root: the full licence text of
|
|
# every Go module the binary is compiled from, and the terms of the artwork and
|
|
# the fonts embedded in it. The repository is the record, so the file lives in
|
|
# the tree and travels with every source archive rather than being attached to
|
|
# a release. The rows are ordered by provenance, not alphabet: the owner's own
|
|
# modules on the forge first, then golang.org, then every other source.
|
|
#
|
|
# Copy this file to scripts/notices.pl, edit the configuration block, and run:
|
|
#
|
|
# perl scripts/notices.pl
|
|
#
|
|
# The rule that decides whether the project has the file at all: a notice
|
|
# exists to reproduce somebody else's terms. A project whose every embedded
|
|
# thing is the owner's own modules, artwork and code has nobody to notice and
|
|
# carries no NOTICE.md; the script says so and writes nothing.
|
|
#
|
|
# Perl builtins only. The one external binary is the Go toolchain, driven in
|
|
# list form so that no path is word-split or globbed, and every failure names
|
|
# what was being attempted.
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Configuration. Replace the values; everything below the block is the
|
|
# standard script and is identical in every repository.
|
|
# ---------------------------------------------------------------------------
|
|
my $binary = "volumen";
|
|
|
|
# Directories whose files ship inside the binary, relative to the repository
|
|
# root. A directory the project does not have simply adds no section.
|
|
my $artwork_dir = "internal/web/static";
|
|
my $fonts_dir = "internal/web/static/fonts";
|
|
|
|
# The faces the fonts directory carries, when it carries third-party ones.
|
|
my $faces_note = <<'FACES';
|
|
Ubuntu and Ubuntu Mono (latin and latin-ext subsets), the interface faces of
|
|
the admin. Copyright 2010, 2011 Canonical Ltd, with the reserved font names
|
|
"Ubuntu" and "Ubuntu Mono"; distributed under the
|
|
[Ubuntu Font Licence 1.0](https://ubuntu.com/legal/font-licence), whose terms
|
|
follow the SIL Open Font Licence 1.1.
|
|
FACES
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
(my $script_dir = $0) =~ s{/[^/]*$}{};
|
|
# Run as `perl notices.pl` and $0 carries no slash: the substitution is a
|
|
# no-op, and the script's own directory is the working directory.
|
|
$script_dir = '.' if !length($script_dir) || $0 !~ m{/};
|
|
my $root = -f "$script_dir/../go.mod" ? "$script_dir/.." : $script_dir;-f "$root/go.mod" or die "no go.mod in $root, so this is not the module root\n";
|
|
my $out_path = "$root/NOTICE.md";
|
|
|
|
sub slurp {
|
|
my ($path) = @_;
|
|
open my $fh, '<', $path or die "cannot read $path: $!\n";
|
|
my $text = do { local $/; <$fh> };
|
|
close $fh;
|
|
return $text;
|
|
}
|
|
|
|
sub spew {
|
|
my ($path, $text) = @_;
|
|
open my $fh, '>', $path or die "cannot write $path: $!\n";
|
|
print $fh $text;
|
|
close $fh or die "cannot flush $path: $!\n";
|
|
}
|
|
|
|
# licence_files returns the licence texts a module directory carries, in a
|
|
# stable order, and dies when there are none: a dependency whose terms cannot
|
|
# be reproduced cannot ship.
|
|
sub licence_files {
|
|
my ($dir) = @_;
|
|
my @found;
|
|
for my $subdir ('', 'LICENSES/', 'licenses/') {
|
|
my $at = length $subdir ? "$dir/$subdir" : $dir;
|
|
next unless -d $at;
|
|
opendir my $dh, $at or die "cannot list $at: $!\n";
|
|
my @names = grep { m{^(?:LICEN[CS]E|COPYING|COPYRIGHT|NOTICE|UNLICENSE)}i && -f "$at/$_" } readdir $dh;
|
|
closedir $dh;
|
|
push @found, map { "$at/$_" } sort @names;
|
|
}
|
|
return @found;
|
|
}
|
|
|
|
# provenance orders the modules: the owner's own forge first, golang.org next,
|
|
# every other source last; alphabetical inside each group.
|
|
sub provenance {
|
|
my ($path) = @_;
|
|
return 0 if $path =~ m{^sourcedock\.dev/};
|
|
return 1 if $path =~ m{^golang\.org/};
|
|
return 2;
|
|
}
|
|
|
|
# The modules the binary is built from: the modules that provide the packages
|
|
# the program imports, which is the same list the toolchain records in the
|
|
# binary. The build list is wider, because it also holds modules that no
|
|
# package of this program compiles, and those need no notice.
|
|
my $main = do {
|
|
open my $m, '-|', qw{go -C}, $root, qw{list -m -f}, '{{.Path}}'
|
|
or die "cannot run go list -m: $!\n";
|
|
my $line = <$m>;
|
|
close $m or die "go list -m failed\n";
|
|
defined $line or die "go list -m named no main module\n";
|
|
chomp $line;
|
|
$line;
|
|
};
|
|
length $main or die "go list -m named no main module\n";
|
|
|
|
my @lines;
|
|
open my $go, '-|', qw{go -C}, $root, qw{list -deps -f}, "{{if .Module}}{{.Module.Path}}\t{{.Module.Version}}\t{{.Module.Dir}}{{end}}", "./cmd/$binary"
|
|
or die "cannot run go list: $!\n";
|
|
while (my $line = <$go>) { push @lines, $line }
|
|
close $go or die "go list -deps failed\n";
|
|
|
|
my (%seen, @modules);
|
|
for my $line (sort @lines) {
|
|
chomp $line;
|
|
next unless length $line;
|
|
my ($path, $version, $dir) = split /\t/, $line, 3;
|
|
$path = '' unless defined $path;
|
|
$version = '' unless defined $version;
|
|
$dir = '' unless defined $dir;
|
|
next if $path eq $main;
|
|
next if $seen{$path}++;
|
|
length $dir or die "no module directory for $path, run go mod download all first\n";
|
|
-d $dir or die "module directory $dir for $path does not exist\n";
|
|
push @modules, { path => $path, version => $version, dir => $dir };
|
|
}
|
|
|
|
my @faces;
|
|
if (opendir my $dh, "$root/$fonts_dir") {
|
|
@faces = sort grep { /\.woff2?\z/ } readdir $dh;
|
|
closedir $dh;
|
|
}
|
|
|
|
# The exemption: everything the artefact carries is the owner's own. The
|
|
# owner's modules (provenance 0) and the owner's artwork need no notice, and a
|
|
# project with nothing else has no file. The script writes nothing, and says
|
|
# why; delete a NOTICE.md left over from a heavier dependency set by hand.
|
|
my @external = grep { provenance($_->{path}) != 0 } @modules;
|
|
if (!@external && !@faces) {
|
|
print "NOTICE.md not needed: every dependency of ./cmd/$binary is the owner's own\n";
|
|
print "and an old NOTICE.md should be removed\n" if -f $out_path;
|
|
exit 0;
|
|
}
|
|
|
|
@modules = sort { provenance($a->{path}) <=> provenance($b->{path}) || $a->{path} cmp $b->{path} } @modules;
|
|
|
|
my $licence_url = {
|
|
'PolyForm-Noncommercial-1.0.0' => 'https://polyformproject.org/licenses/noncommercial/1.0.0',
|
|
'CC-BY-NC-4.0' => 'https://creativecommons.org/licenses/by-nc/4.0/',
|
|
'MIT' => 'https://opensource.org/license/mit',
|
|
};
|
|
|
|
my @t = gmtime;
|
|
my $stamp = sprintf '%04d-%02d-%02d', $t[5] + 1900, $t[4] + 1, $t[3];
|
|
|
|
my $out = <<'HEAD';
|
|
# Third-party notices
|
|
|
|
The project is under the licence in [LICENSE](LICENSE). Everything else that
|
|
a built binary carries is here: the Go modules it is compiled from, with each
|
|
licence reproduced verbatim, and the terms of the artwork and the fonts
|
|
embedded in it. The owner's own modules come first, golang.org next, every
|
|
other source last. Every licence text below is quoted inside a code block, so
|
|
a Markdown renderer cannot reflow it.
|
|
|
|
HEAD
|
|
|
|
$out .= <<"HEAD";
|
|
Generated on $stamp by `perl scripts/notices.pl`, which takes the module list
|
|
from \`go list -deps ./cmd/$binary\`, so it names what the toolchain actually
|
|
links rather than the wider build list. Run it again after a dependency changes.
|
|
|
|
HEAD
|
|
|
|
for my $module (@modules) {
|
|
my @files = licence_files($module->{dir});
|
|
@files or die "no licence file found for $module->{path} in $module->{dir}\n";
|
|
my $name = length $module->{version}
|
|
? "$module->{path} $module->{version}"
|
|
: $module->{path};
|
|
$out .= "## $name\n\n";
|
|
for my $file (@files) {
|
|
if (@files > 1) {
|
|
(my $label = $file) =~ s{^\Q$module->{dir}\E/?}{};
|
|
$out .= "**$label**\n\n";
|
|
}
|
|
my $text = slurp($file);
|
|
$text =~ s/\s+\z//;
|
|
$out .= "```text\n$text\n```\n\n";
|
|
}
|
|
}
|
|
|
|
my @svgs;
|
|
if (opendir my $ah, "$root/$artwork_dir") {
|
|
@svgs = sort grep { /\.svg\z/ } readdir $ah;
|
|
closedir $ah;
|
|
}
|
|
if (@svgs) {
|
|
$out .= "## Artwork embedded in the binary\n\n";
|
|
for my $name (@svgs) {
|
|
my $head = slurp("$root/$artwork_dir/$name");
|
|
my ($copy) = $head =~ m{^<!--\s*(Copyright.*?)\s*-->\s*$}m;
|
|
my ($spdx) = $head =~ m{^<!--\s*SPDX-License-Identifier:\s*(\S+)\s*-->$}m;
|
|
defined $copy or die "no copyright line in $artwork_dir/$name\n";
|
|
defined $spdx or die "no SPDX line in $artwork_dir/$name\n";
|
|
my $url = $licence_url->{$spdx} or die "no licence URL known for $spdx\n";
|
|
$out .= "### $name\n\n$copy\n\n[$spdx]($url)\n\n";
|
|
}
|
|
}
|
|
|
|
if (@faces) {
|
|
$out .= "## Fonts embedded in the binary\n\n";
|
|
(my $note = $faces_note) =~ s/\s+\z//;
|
|
$out .= "$note\n\nThe files carried:\n\n";
|
|
$out .= "- $_\n" for @faces;
|
|
$out .= "\n";
|
|
}
|
|
|
|
spew($out_path, $out);
|
|
printf "wrote NOTICE.md (%d module(s), %d artwork file(s), %d font file(s))\n",
|
|
scalar @modules, scalar @svgs, scalar @faces;
|