зеркало из https://github.com/github/licensed.git
update manifest source
- simplifies local dependency class as a Licensee Project - better license matching on source comments!
This commit is contained in:
Родитель
224e4ecf09
Коммит
3ed3081c1b
|
@ -10,10 +10,11 @@ module Licensed
|
||||||
|
|
||||||
def enumerate_dependencies
|
def enumerate_dependencies
|
||||||
packages.map do |package_name, sources|
|
packages.map do |package_name, sources|
|
||||||
Licensed::Sources::Manifest::Dependency.new(sources,
|
Licensed::Sources::Manifest::Dependency.new(
|
||||||
@config.root,
|
name: package_name,
|
||||||
package_license(package_name),
|
path: configured_license_path(package_name) || sources_license_path(sources),
|
||||||
{
|
sources: sources,
|
||||||
|
metadata: {
|
||||||
"type" => Manifest.type,
|
"type" => Manifest.type,
|
||||||
"name" => package_name,
|
"name" => package_name,
|
||||||
"version" => package_version(sources)
|
"version" => package_version(sources)
|
||||||
|
@ -32,7 +33,7 @@ module Licensed
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns the license path for a package specified in the configuration.
|
# Returns the license path for a package specified in the configuration.
|
||||||
def package_license(package_name)
|
def configured_license_path(package_name)
|
||||||
license_path = @config.dig("manifest", "licenses", package_name)
|
license_path = @config.dig("manifest", "licenses", package_name)
|
||||||
return unless license_path
|
return unless license_path
|
||||||
|
|
||||||
|
@ -41,6 +42,25 @@ module Licensed
|
||||||
license_path
|
license_path
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Returns the top-most directory that is common to all paths in `sources`
|
||||||
|
def sources_license_path(sources)
|
||||||
|
# if there is more than one source, try to find a directory common to
|
||||||
|
# all sources
|
||||||
|
if sources.size > 1
|
||||||
|
common_prefix = Pathname.common_prefix(*sources).to_path
|
||||||
|
|
||||||
|
# don't allow the workspace root to be used as common prefix
|
||||||
|
# the project this is run for should be excluded from the manifest,
|
||||||
|
# or ignored in the config. any license in the root should be ignored.
|
||||||
|
return common_prefix if common_prefix != @config.root
|
||||||
|
end
|
||||||
|
|
||||||
|
# use the first (or only) sources directory to find license information
|
||||||
|
source = sources.first
|
||||||
|
return File.dirname(source) if File.file?(source)
|
||||||
|
source
|
||||||
|
end
|
||||||
|
|
||||||
# Returns a map of package names -> array of full source paths found
|
# Returns a map of package names -> array of full source paths found
|
||||||
# in the app manifest
|
# in the app manifest
|
||||||
def packages
|
def packages
|
||||||
|
@ -169,87 +189,44 @@ module Licensed
|
||||||
)
|
)
|
||||||
/imx.freeze
|
/imx.freeze
|
||||||
|
|
||||||
def initialize(sources, root, license_path, metadata = {})
|
def initialize(name:, path:, sources:, metadata: {})
|
||||||
@sources = sources
|
@sources = sources
|
||||||
license_path ||= sources_license_path(sources, root)
|
super(name: name, path: path, metadata: metadata)
|
||||||
super license_path, metadata
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Detects license information and sets it on this dependency object.
|
def project_files
|
||||||
# After calling `detect_license!``, the license is set at
|
files = super
|
||||||
# `dependency["license"]` and legal text is set to `dependency.text`
|
files.concat(source_files) if files.empty?
|
||||||
def detect_license!
|
files
|
||||||
# if no license key is found for the project, try to create a
|
end
|
||||||
# temporary LICENSE file from unique source file license headers
|
|
||||||
if license_key == "none"
|
|
||||||
tmp_license_file = write_license_from_source_licenses(self.path, @sources)
|
|
||||||
reset_license!
|
|
||||||
end
|
|
||||||
|
|
||||||
super
|
def source_files
|
||||||
ensure
|
@source_files ||= begin
|
||||||
File.delete(tmp_license_file) if tmp_license_file && File.exist?(tmp_license_file)
|
@sources.select { |f| File.file?(f) }
|
||||||
|
.map { |f| File.read(f) }
|
||||||
|
.flat_map { |content| content.scan(HEADER_LICENSE_REGEX) }
|
||||||
|
.map { |match| match[0] }
|
||||||
|
.map { |comment| source_comment_text(comment) }
|
||||||
|
.compact
|
||||||
|
.map { |content| Licensee::ProjectFiles::LicenseFile.new(content) }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
# Returns the top-most directory that is common to all paths in `sources`
|
# Returns the comment text with leading * and whitespace stripped
|
||||||
def sources_license_path(sources, root)
|
def source_comment_text(comment)
|
||||||
# return the source directory if there is only one source given
|
indent = nil
|
||||||
return source_directory(sources[0]) if sources.size == 1
|
comment.lines.map do |line|
|
||||||
|
# find the length of the indent as the number of characters
|
||||||
|
# until the first word character
|
||||||
|
indent ||= line[/\A([^\w]*)\w/, 1]&.size
|
||||||
|
|
||||||
common_prefix = Pathname.common_prefix(*sources).to_path
|
# insert newline for each line until a word character is found
|
||||||
|
next "\n" unless indent
|
||||||
|
|
||||||
# don't allow the workspace root to be used as common prefix
|
line[/([^\w\r\n]{0,#{indent}})(.*)/m, 2]
|
||||||
# the project this is run for should be excluded from the manifest,
|
end.join
|
||||||
# or ignored in the config. any license in the root should be ignored.
|
|
||||||
return common_prefix if common_prefix != root
|
|
||||||
|
|
||||||
# use the first source directory as the license path.
|
|
||||||
source_directory(sources.first)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns the directory for the source. Checks whether the source
|
|
||||||
# is a file or a directory
|
|
||||||
def source_directory(source)
|
|
||||||
return File.dirname(source) if File.file?(source)
|
|
||||||
source
|
|
||||||
end
|
|
||||||
|
|
||||||
# Writes any licenses found in source file comments to a LICENSE
|
|
||||||
# file at `dir`
|
|
||||||
# Returns the path to the license file
|
|
||||||
def write_license_from_source_licenses(dir, sources)
|
|
||||||
license_path = File.join(dir, "LICENSE")
|
|
||||||
File.open(license_path, "w") do |f|
|
|
||||||
licenses = source_comment_licenses(sources).uniq
|
|
||||||
f.puts(licenses.join("\n#{LICENSE_SEPARATOR}\n"))
|
|
||||||
end
|
|
||||||
|
|
||||||
license_path
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns a list of unique licenses parsed from source comments
|
|
||||||
def source_comment_licenses(sources)
|
|
||||||
comments = sources.select { |s| File.file?(s) }.flat_map do |source|
|
|
||||||
content = File.read(source)
|
|
||||||
content.scan(HEADER_LICENSE_REGEX).map { |match| match[0] }
|
|
||||||
end
|
|
||||||
|
|
||||||
comments.map do |comment|
|
|
||||||
# strip leading "*" and whitespace
|
|
||||||
indent = nil
|
|
||||||
comment.lines.map do |line|
|
|
||||||
# find the length of the indent as the number of characters
|
|
||||||
# until the first word character
|
|
||||||
indent ||= line[/\A([^\w]*)\w/, 1]&.size
|
|
||||||
|
|
||||||
# insert newline for each line until a word character is found
|
|
||||||
next "\n" unless indent
|
|
||||||
|
|
||||||
line[/([^\w\r\n]{0,#{indent}})(.*)/m, 2]
|
|
||||||
end.join
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
test/fixtures/manifest/test_1.c: manifest_test
|
test/fixtures/manifest/test_1.c: manifest_test
|
||||||
test/fixtures/manifest/subfolder/test_2.c: manifest_test
|
test/fixtures/manifest/subfolder/test_2.c: manifest_test
|
||||||
script/console: other
|
|
||||||
test/fixtures/manifest/single_license_header/source.c: bsd3_single_header_license
|
test/fixtures/manifest/single_license_header/source.c: bsd3_single_header_license
|
||||||
test/fixtures/manifest/single_license_header/source_2.c: bsd3_single_header_license
|
test/fixtures/manifest/single_license_header/source_2.c: bsd3_single_header_license
|
||||||
test/fixtures/manifest/multiple_license_headers/source.c: bsd3_multi_header_license
|
test/fixtures/manifest/multiple_license_headers/source.c: bsd3_multi_header_license
|
||||||
|
|
|
@ -31,22 +31,10 @@ describe Licensed::Sources::Manifest do
|
||||||
|
|
||||||
describe "dependencies" do
|
describe "dependencies" do
|
||||||
it "includes dependencies from the manifest" do
|
it "includes dependencies from the manifest" do
|
||||||
dep = source.dependencies.detect { |d| d["name"] == "manifest_test" }
|
dep = source.dependencies.detect { |d| d.name == "manifest_test" }
|
||||||
assert dep
|
assert dep
|
||||||
assert_equal "manifest", dep["type"]
|
assert_equal "manifest", dep.data["type"]
|
||||||
assert dep["version"] # version comes from git, just make sure its there
|
assert dep.data["version"] # version comes from git, just make sure its there
|
||||||
end
|
|
||||||
|
|
||||||
describe "paths" do
|
|
||||||
it "finds the common folder path for the dependency" do
|
|
||||||
dep = source.dependencies.detect { |d| d["name"] == "manifest_test" }
|
|
||||||
assert_equal fixtures, dep.path
|
|
||||||
end
|
|
||||||
|
|
||||||
it "uses the first source folder if there is no common path" do
|
|
||||||
dep = source.dependencies.detect { |d| d["name"] == "other" }
|
|
||||||
assert dep.path.end_with?("script")
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "uses a license specified in the configuration if provided" do
|
it "uses a license specified in the configuration if provided" do
|
||||||
|
@ -56,53 +44,39 @@ describe Licensed::Sources::Manifest do
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dep = source.dependencies.detect { |d| d["name"] == "manifest_test" }
|
dep = source.dependencies.detect { |d| d.name == "manifest_test" }
|
||||||
assert dep
|
assert dep
|
||||||
dep.detect_license!
|
assert_equal "mit", dep.data["license"]
|
||||||
assert_equal "mit", dep["license"]
|
|
||||||
|
|
||||||
license_path = File.join(config.root, config.dig("manifest", "licenses", "manifest_test"))
|
license_path = File.join(config.root, config.dig("manifest", "licenses", "manifest_test"))
|
||||||
assert_equal File.read(license_path).strip, dep.text
|
assert_includes dep.data.licenses, File.read(license_path)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "prefers licenses from license files" do
|
it "prefers licenses from license files" do
|
||||||
dep = source.dependencies.detect { |d| d["name"] == "mit_license_file" }
|
dep = source.dependencies.detect { |d| d.name == "mit_license_file" }
|
||||||
assert dep
|
assert dep
|
||||||
dep.detect_license!
|
assert_equal "mit", dep.data["license"]
|
||||||
assert_equal "mit", dep["license"]
|
refute_empty dep.data.licenses
|
||||||
refute_nil dep.text
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "detects license from source header comments if license files are not found" do
|
it "detects license from source header comments if license files are not found" do
|
||||||
dep = source.dependencies.detect { |d| d["name"] == "bsd3_single_header_license" }
|
dep = source.dependencies.detect { |d| d.name == "bsd3_single_header_license" }
|
||||||
assert dep
|
assert dep
|
||||||
dep.detect_license!
|
assert_equal "bsd-3-clause", dep.data["license"]
|
||||||
assert_equal "bsd-3-clause", dep["license"]
|
assert_equal 1, dep.data.licenses.uniq.size
|
||||||
refute_nil dep.text
|
|
||||||
refute dep.text.include?(Licensed::License::LICENSE_SEPARATOR)
|
|
||||||
|
|
||||||
# verify that the license file was removed after evaluation
|
|
||||||
refute File.exist?(File.join(dep.path, "LICENSE"))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "detects unique license content from multiple headers" do
|
it "detects unique license content from multiple headers" do
|
||||||
dep = source.dependencies.detect { |d| d["name"] == "bsd3_multi_header_license" }
|
dep = source.dependencies.detect { |d| d.name == "bsd3_multi_header_license" }
|
||||||
assert dep
|
assert dep
|
||||||
dep.detect_license!
|
assert_equal "bsd-3-clause", dep.data["license"]
|
||||||
# because there are different licenses/copyrights that need to be included
|
assert_equal 2, dep.data.licenses.uniq.size
|
||||||
# we aren't able to specify that the actual license content is equivalent
|
|
||||||
# so we are left with "other"
|
|
||||||
assert_equal "other", dep["license"]
|
|
||||||
refute_nil dep.text
|
|
||||||
assert dep.text.include?(Licensed::License::LICENSE_SEPARATOR)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "preserves legal notices when detecting license content from comments" do
|
it "preserves legal notices when detecting license content from comments" do
|
||||||
dep = source.dependencies.detect { |d| d["name"] == "notices" }
|
dep = source.dependencies.detect { |d| d.name == "notices" }
|
||||||
assert dep
|
assert dep
|
||||||
dep.detect_license!
|
refute_empty dep.data.notices
|
||||||
refute_nil dep.text
|
|
||||||
assert dep.text.include?(dep.notices.join("\n#{Licensed::License::TEXT_SEPARATOR}\n").strip)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Загрузка…
Ссылка в новой задаче