Skip to content

Commit ac07463

Browse files
committed
feat: Add license and SPDX identifier
Fixes - #12 - #6 Signed-off-by: Dominik Gedon <dominik.gedon@suse.com>
1 parent 50edb03 commit ac07463

10 files changed

Lines changed: 408 additions & 23 deletions

File tree

lib/template/package.spec

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ cp -a <%= output %> %{buildroot}%{_javadir}/<%= File.basename(output) %>
6666

6767
%files
6868
%defattr(-,root,root)
69+
<% license_files.each do |file| %>
70+
%doc <%= file %>
71+
<% end %>
6972
%{_javadir}/*
7073

7174
%changelog

lib/tetra.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
# third party libraries
2121
require "clamp"
2222
require "text"
23+
require "yaml"
2324
require "zip"
2425

2526
# base components
@@ -48,6 +49,7 @@
4849
require "tetra/pom_getter"
4950

5051
# package building
52+
require "tetra/license_mapper"
5153
require "tetra/packages/speccable"
5254
require "tetra/packages/scriptable"
5355
require "tetra/packages/kit_package"

lib/tetra/constants.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@
44
module Tetra
55
CCOLLECTIONS = "commons-collections4-4.5.0-M2-src".freeze
66
CHUNK_SEPARATOR_VERSION_MATCHER = /[.\-_ ~,]/ # Constant regex for splitting version chunks
7+
LICENSE_MAP_PATH = File.join(__dir__, "data", "license_map.yml").freeze
78
end

lib/tetra/data/license_map.yml

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# Mappings of Raw Maven License Names -> SPDX Identifiers
2+
# Based on https://spdx.org/licenses/ and openSUSE/Fedora standards
3+
4+
# --- Apache Licenses ---
5+
"The Apache Software License, Version 2.0": "Apache-2.0"
6+
"Apache License, Version 2.0": "Apache-2.0"
7+
"Apache License Version 2.0": "Apache-2.0"
8+
"Apache 2.0": "Apache-2.0"
9+
"Apache License 2.0": "Apache-2.0"
10+
"AL 2.0": "Apache-2.0"
11+
"Apache Software License - Version 2.0": "Apache-2.0"
12+
"The Apache License, Version 2.0": "Apache-2.0"
13+
"Apache-2.0": "Apache-2.0"
14+
15+
"Apache License, Version 1.1": "Apache-1.1"
16+
"The Apache Software License, Version 1.1": "Apache-1.1"
17+
"Apache 1.1": "Apache-1.1"
18+
19+
"Apache License, Version 1.0": "Apache-1.0"
20+
21+
# --- MIT Licenses ---
22+
"The MIT License": "MIT"
23+
"MIT License": "MIT"
24+
"MIT": "MIT"
25+
"MIT license": "MIT"
26+
"The MIT License (MIT)": "MIT"
27+
28+
# --- Eclipse Public Licenses ---
29+
"Eclipse Public License 1.0": "EPL-1.0"
30+
"Eclipse Public License - v 1.0": "EPL-1.0"
31+
"Eclipse Public License, Version 1.0": "EPL-1.0"
32+
"EPL 1.0": "EPL-1.0"
33+
34+
"Eclipse Public License 2.0": "EPL-2.0"
35+
"Eclipse Public License - v 2.0": "EPL-2.0"
36+
"EPL 2.0": "EPL-2.0"
37+
38+
# --- GPL (General Public License) ---
39+
"GNU General Public License, version 2": "GPL-2.0-only"
40+
"GNU General Public License, Version 2": "GPL-2.0-only"
41+
"GPL 2.0": "GPL-2.0-only"
42+
"GPLv2": "GPL-2.0-only"
43+
"The GNU General Public License, Version 2": "GPL-2.0-only"
44+
45+
"GNU General Public License, version 2 (GPLv2)": "GPL-2.0-only"
46+
"GNU General Public License, version 3": "GPL-3.0-only"
47+
"GPL 3.0": "GPL-3.0-only"
48+
"GPLv3": "GPL-3.0-only"
49+
50+
# --- LGPL (Lesser General Public License) ---
51+
"GNU Lesser General Public License": "LGPL-2.1-only"
52+
"GNU Lesser General Public License, version 2.1": "LGPL-2.1-only"
53+
"LGPL 2.1": "LGPL-2.1-only"
54+
"LGPLv2.1": "LGPL-2.1-only"
55+
"The GNU Lesser General Public License, Version 2.1": "LGPL-2.1-only"
56+
57+
"GNU Lesser General Public License, version 3": "LGPL-3.0-only"
58+
"LGPL 3.0": "LGPL-3.0-only"
59+
"LGPLv3": "LGPL-3.0-only"
60+
61+
# --- BSD Licenses ---
62+
"The BSD License": "BSD-2-Clause" # Ambiguous, but often 2-clause in modern Java
63+
"BSD License": "BSD-2-Clause"
64+
"BSD-2-Clause": "BSD-2-Clause"
65+
"Two-clause BSD license": "BSD-2-Clause"
66+
"The BSD 2-Clause License": "BSD-2-Clause"
67+
68+
"New BSD License": "BSD-3-Clause"
69+
"BSD 3-Clause": "BSD-3-Clause"
70+
"BSD 3-Clause License": "BSD-3-Clause"
71+
"The BSD 3-Clause License": "BSD-3-Clause"
72+
"Revised BSD License": "BSD-3-Clause"
73+
"Modified BSD License": "BSD-3-Clause"
74+
75+
"BSD 0-Clause": "0BSD"
76+
"0BSD": "0BSD"
77+
78+
# --- CDDL (Common Development and Distribution License) ---
79+
"Common Development and Distribution License": "CDDL-1.0"
80+
"CDDL 1.0": "CDDL-1.0"
81+
"CDDL-1.0": "CDDL-1.0"
82+
"COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0": "CDDL-1.0"
83+
84+
"Common Development and Distribution License 1.1": "CDDL-1.1"
85+
"CDDL 1.1": "CDDL-1.1"
86+
87+
# --- MPL (Mozilla Public License) ---
88+
"Mozilla Public License Version 1.1": "MPL-1.1"
89+
"MPL 1.1": "MPL-1.1"
90+
91+
"Mozilla Public License Version 2.0": "MPL-2.0"
92+
"MPL 2.0": "MPL-2.0"
93+
"The Mozilla Public License, Version 2.0": "MPL-2.0"
94+
95+
# --- ISC License ---
96+
"ISC License": "ISC"
97+
"The ISC License": "ISC"
98+
99+
# --- Creative Commons ---
100+
"Creative Commons Zero v1.0 Universal": "CC0-1.0"
101+
"CC0 1.0 Universal": "CC0-1.0"
102+
"Public Domain": "CC0-1.0" # Approximation for Public Domain in SPDX
103+
104+
"Creative Commons Attribution 3.0": "CC-BY-3.0"
105+
"CC BY 3.0": "CC-BY-3.0"
106+
107+
"Creative Commons Attribution 4.0": "CC-BY-4.0"
108+
"CC BY 4.0": "CC-BY-4.0"
109+
110+
# --- Other Common Java Licenses ---
111+
"The JSON License": "JSON"
112+
"JSON": "JSON"
113+
114+
"Unlicense": "Unlicense"
115+
"The Unlicense": "Unlicense"
116+
117+
"WTFPL": "WTFPL"
118+
"Do What The F*ck You Want To Public License": "WTFPL"
119+
120+
"PostgreSQL License": "PostgreSQL"
121+
"The PostgreSQL License": "PostgreSQL"
122+
123+
"Zlib License": "Zlib"
124+
"The zlib/libpng License": "Zlib"

lib/tetra/license_mapper.rb

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# frozen_string_literal: true
2+
3+
module Tetra
4+
# Handles mapping raw license names to SPDX identifiers
5+
class LicenseMapper
6+
class << self
7+
def map(raw_name)
8+
return "unknown" if raw_name.nil?
9+
10+
mapping[raw_name.strip] || raw_name.strip
11+
end
12+
13+
def reset!
14+
@mapping = nil
15+
end
16+
17+
private
18+
19+
def mapping
20+
@mapping ||= begin
21+
load_mapping
22+
rescue Errno::ENOENT, Psych::SyntaxError, Psych::DisallowedClass, IOError => e
23+
warn "Warning: failed to load license mapping from #{Tetra::LICENSE_MAP_PATH}: #{e.class}: #{e.message}"
24+
{}
25+
end
26+
end
27+
28+
def load_mapping
29+
YAML.safe_load_file(Tetra::LICENSE_MAP_PATH) || {}
30+
end
31+
end
32+
end
33+
end

lib/tetra/packages/package.rb

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
# frozen_string_literal: true
22

3-
require "forwardable"
4-
53
module Tetra
64
# represents a Java project packaged in tetra
75
class Package
@@ -15,7 +13,6 @@ class Package
1513
def_delegator :@project, :src_archive
1614
def_delegator :@kit, :name, :kit_name
1715
def_delegator :@kit, :version, :kit_version
18-
def_delegator :@pom, :license_name, :license
1916
def_delegator :@pom, :url
2017
def_delegator :@pom, :group_id
2118
def_delegator :@pom, :version
@@ -29,6 +26,37 @@ def initialize(project, pom_path = nil, filter = nil, patches = [])
2926
@patches = patches.map { |f| File.basename(f) }
3027
end
3128

29+
def license
30+
Tetra::LicenseMapper.map(@pom.license_name)
31+
end
32+
33+
# Scans for license files in the src/ directory (Issue #12)
34+
def license_files
35+
@project.from_directory do
36+
# Tetra unpacks sources into 'src/', so we search there.
37+
# We manually check filenames with Ruby regex to ensure reliable
38+
# case-insensitive matching across different OS filesystems (Linux vs macOS).
39+
40+
candidates = Dir.glob("src/*")
41+
42+
files = candidates.select do |file|
43+
next unless File.file?(file)
44+
45+
basename = File.basename(file)
46+
47+
# Match standard license filenames (case insensitive)
48+
# Equivalent to: LICENSE*, COPYING*, COPYRIGHT*
49+
basename.match?(/^LICENSE/i) ||
50+
basename.match?(/^COPYING/i) ||
51+
basename.match?(/^COPYRIGHT/i)
52+
end
53+
54+
# Return just the filenames (e.g. "LICENSE") so the spec file
55+
# (which runs inside src/ due to %setup -n src) can reference them directly.
56+
files.map { |f| File.basename(f) }.uniq.sort
57+
end
58+
end
59+
3260
def artifact_ids
3361
@pom.modules.any? ? @pom.modules : [@pom.artifact_id]
3462
end
@@ -51,18 +79,18 @@ def outputs
5179
end
5280

5381
def cleanup_description(raw, max_length)
82+
return "" if raw.nil?
83+
5484
# Normalize spaces (collapse multiple spaces/newlines to single space)
5585
clean = raw.gsub(/\s+/, " ").strip
5686

5787
# Truncate to max_length
5888
clean = clean[0, max_length]
5989

6090
# Remove the last word if it looks cut off (ends in letters, not punctuation)
61-
# Note: This assumes descriptions usually end with punctuation.
6291
clean = clean.sub(/\s\w+\z/, "")
6392

64-
# Remove ALL trailing dots efficiently (Security Fix for ReDoS)
65-
# Replaces clean.sub(/\.+\z/, "")
93+
# Remove ALL trailing dots efficiently
6694
clean = clean.chomp(".") while clean.end_with?(".")
6795

6896
clean
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# frozen_string_literal: true
2+
3+
require "spec_helper"
4+
5+
describe "tetra generate-spec license handling", type: :aruba do
6+
def create_pom(group_id, artifact_id, version, license_name)
7+
<<~XML
8+
<project>
9+
<modelVersion>4.0.0</modelVersion>
10+
<groupId>#{group_id}</groupId>
11+
<artifactId>#{artifact_id}</artifactId>
12+
<version>#{version}</version>
13+
<packaging>jar</packaging>
14+
<licenses>
15+
<license>
16+
<name>#{license_name}</name>
17+
<url>http://example.com/license</url>
18+
</license>
19+
</licenses>
20+
</project>
21+
XML
22+
end
23+
24+
let(:test_cases) do
25+
{
26+
"The Apache Software License, Version 2.0" => "Apache-2.0",
27+
"The MIT License" => "MIT",
28+
"Eclipse Public License 1.0" => "EPL-1.0",
29+
"GNU General Public License, version 2" => "GPL-2.0-only",
30+
"GNU Lesser General Public License" => "LGPL-2.1-only",
31+
"Unmapped Custom License" => "Unmapped Custom License"
32+
}
33+
end
34+
35+
it "detects license names and converts them to SPDX" do
36+
test_cases.each do |raw_name, expected_spdx|
37+
project_name = "proj-#{expected_spdx.downcase.gsub(/[^a-z0-9]/, "")}"
38+
version = "1.0"
39+
group_id = "com.example"
40+
41+
write_file("pom.xml", create_pom(group_id, project_name, version, raw_name))
42+
write_file("LICENSE", "License text")
43+
write_file("COPYING", "Copying text")
44+
write_file("README", "Readme text")
45+
46+
tarball_name = "#{project_name}.tar"
47+
run_command_and_stop("tar -cvf #{tarball_name} pom.xml LICENSE COPYING README")
48+
49+
run_command_and_stop("tetra init #{project_name} #{tarball_name}")
50+
51+
cd(project_name) do
52+
run_command_and_stop("git config user.email 'test@example.com'")
53+
run_command_and_stop("git config user.name 'Test User'")
54+
55+
# --- NON-INTERACTIVE DRY-RUN ---
56+
# We use a heredoc to pipe the commands directly.
57+
run_command("tetra dry-run")
58+
last_command_started.write <<~COMMANDS
59+
touch #{project_name}-#{version}.jar
60+
echo 'changes' >> README
61+
exit 0
62+
COMMANDS
63+
64+
# Stop will wait for the pipe to close and the process to exit naturally
65+
last_command_started.stop
66+
expect(last_command_started).to be_successfully_executed
67+
68+
run_command_and_stop("tetra generate-spec src/pom.xml")
69+
70+
possible_paths = [
71+
"packages/#{project_name}/package.spec",
72+
"packages/#{project_name}/#{project_name}.spec"
73+
]
74+
spec_path = possible_paths.find { |path| file?(path) }
75+
76+
expect(spec_path).not_to be_nil, "Spec file missing. Checked: #{possible_paths}"
77+
78+
spec_content = read(spec_path).join("\n")
79+
expect(spec_content).to match(/License:\s+#{Regexp.escape(expected_spdx)}/)
80+
expect(spec_content).to include("%doc LICENSE")
81+
expect(spec_content).to include("%doc COPYING")
82+
end
83+
end
84+
end
85+
end

0 commit comments

Comments
 (0)