Class: Licensed::Sources::Manifest

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

Defined Under Namespace

Classes: Dependency

Constant Summary

Constants included from ContentVersioning

ContentVersioning::CONTENTS, ContentVersioning::GIT

Instance Attribute Summary

Attributes inherited from Source

#config

Instance Method Summary collapse

Methods included from ContentVersioning

#contents_hash, #contents_version, #git_version, #version_strategy

Methods inherited from Source

#dependencies, full_type, #ignored?, inherited, #initialize, register_source, require_matched_dependency_version, #source_config, type, type_and_version

Constructor Details

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

Instance Method Details

#configured_dependenciesObject

Returns the project dependencies specified from the licensed configuration



129
130
131
132
133
134
135
136
137
138
139
# File 'lib/licensed/sources/manifest.rb', line 129

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

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

#configured_license_path(package_name) ⇒ Object

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



30
31
32
33
34
35
36
37
# File 'lib/licensed/sources/manifest.rb', line 30

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)


10
11
12
# File 'lib/licensed/sources/manifest.rb', line 10

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

#enumerate_dependenciesObject



14
15
16
17
18
19
20
21
22
23
24
25
26
27
# File 'lib/licensed/sources/manifest.rb', line 14

def enumerate_dependencies
  Parallel.map(packages) do |package_name, sources|
    Licensed::Sources::Manifest::Dependency.new(
      name: package_name,
      version: contents_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.



148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/licensed/sources/manifest.rb', line 148

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

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

#generate_manifestObject

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



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

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)


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

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



142
143
144
# File 'lib/licensed/sources/manifest.rb', line 142

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

#manifestObject

Returns parsed or generated manifest data for the app



69
70
71
72
73
74
75
76
77
78
# File 'lib/licensed/sources/manifest.rb', line 69

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



81
82
83
84
85
86
# File 'lib/licensed/sources/manifest.rb', line 81

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

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

#packagesObject

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



60
61
62
63
64
65
66
# File 'lib/licensed/sources/manifest.rb', line 60

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

#sources_license_path(sources) ⇒ Object

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



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/licensed/sources/manifest.rb', line 40

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

#tracked_filesObject

Returns all tracked files in the project as the intersection of what git tracks and the files in the project



166
167
168
169
# File 'lib/licensed/sources/manifest.rb', line 166

def tracked_files
  @tracked_files ||= Set.new(Array(Licensed::Git.files)) &
                     Set.new(Dir.glob("**/*", File::FNM_DOTMATCH, base: config.root))
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:



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/licensed/sources/manifest.rb', line 104

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