Skip to content

Commit 6e6e889

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 6e6e889

10 files changed

Lines changed: 436 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: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,12 @@
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
8+
LICENSE_MAPPINGS = {
9+
"The Apache Software License, Version 2.0" => "Apache-2.0",
10+
"The MIT License" => "MIT",
11+
"Eclipse Public License 1.0" => "EPL-1.0",
12+
"GNU General Public License, version 2" => "GPL-2.0-only",
13+
"GNU Lesser General Public License" => "LGPL-2.1-only"
14+
}.freeze
715
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: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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? || raw_name.strip.empty?
9+
10+
name = raw_name.strip
11+
mapping[name] || name
12+
end
13+
14+
def reset!
15+
@mapping = nil
16+
end
17+
18+
private
19+
20+
def mapping
21+
@mapping ||= begin
22+
load_mapping
23+
rescue Errno::ENOENT, Psych::SyntaxError, Psych::DisallowedClass, IOError => e
24+
warn "Warning: failed to load license mapping from #{Tetra::LICENSE_MAP_PATH}: #{e.class}: #{e.message}"
25+
{}
26+
end
27+
end
28+
29+
def load_mapping
30+
YAML.safe_load_file(Tetra::LICENSE_MAP_PATH) || {}
31+
end
32+
end
33+
end
34+
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? || raw.strip.empty?
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: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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+
Tetra::LICENSE_MAPPINGS.each do |raw_name, expected_spdx|
25+
it "correctly maps '#{raw_name}' to '#{expected_spdx}'" do
26+
project_name = "proj-#{expected_spdx.downcase.gsub(/[^a-z0-9]/, "")}"
27+
version = "1.0"
28+
group_id = "com.example"
29+
30+
write_file("pom.xml", create_pom(group_id, project_name, version, raw_name))
31+
write_file("LICENSE", "License text")
32+
write_file("COPYING", "Copying text")
33+
write_file("README", "Readme text")
34+
35+
tarball_name = "#{project_name}.tar"
36+
run_command_and_stop("tar -cvf #{tarball_name} pom.xml LICENSE COPYING README")
37+
38+
run_command_and_stop("tetra init #{project_name} #{tarball_name}")
39+
40+
cd(project_name) do
41+
# NOTE: Identity is already set in spec_helper's around hook,
42+
# but we can keep these for explicit project-level config
43+
run_command_and_stop("git config user.email 'test@example.com'")
44+
run_command_and_stop("git config user.name 'Test User'")
45+
46+
# --- NON-INTERACTIVE DRY-RUN ---
47+
run_command("tetra dry-run")
48+
last_command_started.write <<~COMMANDS
49+
touch #{project_name}-#{version}.jar
50+
echo 'changes' >> README
51+
exit 0
52+
COMMANDS
53+
54+
last_command_started.stop
55+
expect(last_command_started).to be_successfully_executed
56+
57+
run_command_and_stop("tetra generate-spec src/pom.xml")
58+
59+
possible_paths = [
60+
"packages/#{project_name}/package.spec",
61+
"packages/#{project_name}/#{project_name}.spec"
62+
]
63+
spec_path = possible_paths.find { |path| file?(path) }
64+
65+
expect(spec_path).not_to be_nil, "Spec file missing. Checked: #{possible_paths}"
66+
67+
spec_content = read(spec_path).join("\n")
68+
expect(spec_content).to match(/License:\s+#{Regexp.escape(expected_spdx)}/)
69+
expect(spec_content).to include("%doc LICENSE")
70+
expect(spec_content).to include("%doc COPYING")
71+
end
72+
end
73+
end
74+
end

0 commit comments

Comments
 (0)