Class: Licensed::Sources::Manifest

Inherits:
Source
  • Object
show all
Defined in:
lib/licensed/sources/manifest.rb

Defined Under Namespace

Classes: Dependency

Instance Attribute Summary

Attributes inherited from Source

#config

Instance Method Summary collapse

Methods inherited from Source

#dependencies, #ignored?, inherited, #initialize, type

Constructor Details

This class inherits a constructor from Licensed::Sources::Source

Instance Method Details

#all_filesObject

Returns all tracked files in the project



176
177
178
179
180
# File 'lib/licensed/sources/manifest.rb', line 176

def all_files
  # remove files if they are tracked but don't exist on the file system
  @all_files ||= Set.new(Licensed::Git.files || [])
                    .delete_if { |f| !File.exist?(f) }
end

#configured_dependenciesObject

Returns the project dependencies specified from the licensed configuration



135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/licensed/sources/manifest.rb', line 135

def configured_dependencies
  @configured_dependencies ||= begin
    dependencies = @config.dig("manifest", "dependencies")&.dup || {}

    dependencies.each do |name, patterns|
      # map glob pattern(s) listed for the dependency to a listing
      # of files that match the patterns and are not excluded
      dependencies[name] = files_from_pattern_list(patterns) & included_files
    end

    dependencies
  end
end

#configured_license_path(package_name) ⇒ Object

Returns the license path for a package specified in the configuration.



36
37
38
39
40
41
42
43
# File 'lib/licensed/sources/manifest.rb', line 36

def configured_license_path(package_name)
  license_path = @config.dig("manifest", "licenses", package_name)
  return unless license_path

  license_path = @config.root.join(license_path)
  return unless license_path.exist?
  license_path
end

#enabled?Boolean

Returns:

  • (Boolean)


7
8
9
# File 'lib/licensed/sources/manifest.rb', line 7

def enabled?
  File.exist?(manifest_path) || generate_manifest?
end

#enumerate_dependenciesObject



11
12
13
14
15
16
17
18
19
20
21
22
23
24
# File 'lib/licensed/sources/manifest.rb', line 11

def enumerate_dependencies
  packages.map do |package_name, sources|
    Licensed::Sources::Manifest::Dependency.new(
      name: package_name,
      version: package_version(sources),
      path: configured_license_path(package_name) || sources_license_path(sources),
      sources: sources,
      metadata: {
        "type"     => Manifest.type,
        "name"     => package_name
      }
    )
  end
end

#files_from_pattern_list(patterns) ⇒ Object

Finds and returns all files in the project that match the glob pattern arguments.



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/licensed/sources/manifest.rb', line 156

def files_from_pattern_list(patterns)
  return Set.new if patterns.nil? || patterns.empty?

  # evaluate all patterns from the project root
  Dir.chdir @config.root do
    Array(patterns).reduce(Set.new) do |files, pattern|
      if pattern.start_with?("!")
        # if the pattern is an exclusion, remove all matching files
        # from the result
        files - Dir.glob(pattern[1..-1], File::FNM_DOTMATCH)
      else
        # if the pattern is an inclusion, add all matching files
        # to the result
        files + Dir.glob(pattern, File::FNM_DOTMATCH)
      end
    end
  end
end

#generate_manifestObject

Returns a manifest of files generated automatically based on patterns set in the licensed configuration file



101
102
103
104
105
106
# File 'lib/licensed/sources/manifest.rb', line 101

def generate_manifest
  verify_configured_dependencies!
  configured_dependencies.each_with_object({}) do |(name, files), hsh|
    files.each { |f| hsh[f] = name }
  end
end

#generate_manifest?Boolean

Returns whether a manifest should be generated automatically

Returns:

  • (Boolean)


95
96
97
# File 'lib/licensed/sources/manifest.rb', line 95

def generate_manifest?
  !File.exist?(manifest_path) && !@config.dig("manifest", "dependencies").nil?
end

#included_filesObject

Returns the set of project files that are included in dependency evaluation



150
151
152
# File 'lib/licensed/sources/manifest.rb', line 150

def included_files
  @sources ||= all_files - files_from_pattern_list(@config.dig("manifest", "exclude"))
end

#manifestObject

Returns parsed or generated manifest data for the app



75
76
77
78
79
80
81
82
83
84
# File 'lib/licensed/sources/manifest.rb', line 75

def manifest
  return generate_manifest if generate_manifest?

  case manifest_path.extname.downcase.delete "."
  when "json"
    JSON.parse(File.read(manifest_path))
  when "yml", "yaml"
    YAML.load_file(manifest_path)
  end
end

#manifest_pathObject

Returns the manifest location for the app



87
88
89
90
91
92
# File 'lib/licensed/sources/manifest.rb', line 87

def manifest_path
  path = @config.dig("manifest", "path")
  return @config.root.join(path) if path

  @config.cache_path.join("manifest.json")
end

#package_version(sources) ⇒ Object

Returns the latest git SHA available from ‘sources`



27
28
29
30
31
32
33
# File 'lib/licensed/sources/manifest.rb', line 27

def package_version(sources)
  return if sources.nil? || sources.empty?

  sources.map { |s| Licensed::Git.version(s) }
         .compact
         .max_by { |sha| Licensed::Git.commit_date(sha) }
end

#packagesObject

Returns a map of package names -> array of full source paths found in the app manifest



66
67
68
69
70
71
72
# File 'lib/licensed/sources/manifest.rb', line 66

def packages
  manifest.each_with_object({}) do |(src, package_name), hsh|
    next if src.nil? || src.empty?
    hsh[package_name] ||= []
    hsh[package_name] << File.join(@config.root, src)
  end
end

#sources_license_path(sources) ⇒ Object

Returns the top-most directory that is common to all paths in ‘sources`



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/licensed/sources/manifest.rb', line 46

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

#verify_configured_dependencies!Object

Verify that the licensed configuration file is valid for the current project. Raises errors for issues found with configuration

Raises:



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/licensed/sources/manifest.rb', line 110

def verify_configured_dependencies!
  # verify that dependencies are configured
  if configured_dependencies.empty?
    raise Source::Error.new("The manifest \"dependencies\" cannot be empty!")
  end

  # verify all included files match a single configured dependency
  errors = included_files.map do |file|
    matches = configured_dependencies.select { |name, files| files.include?(file) }
                                     .map { |name, files| name }
    case matches.size
    when 0
      "#{file} did not match a configured dependency"
    when 1
      nil
    else
      "#{file} matched multiple configured dependencies: #{matches.join(", ")}"
    end
  end

  errors.compact!
  raise Source::Error.new(errors.join($/)) if errors.any?
end