license_patrol 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. checksums.yaml +7 -0
  2. data/.force-build +0 -0
  3. data/.github/dependabot.yml +16 -0
  4. data/.github/workflows/install-test-tools.sh +28 -0
  5. data/.github/workflows/license_patrol.yml +13 -0
  6. data/.gitignore +13 -0
  7. data/.pre-commit-hooks.yaml +10 -0
  8. data/.rspec +1 -0
  9. data/.rubocop.yml +84 -0
  10. data/CHANGELOG.md +1070 -0
  11. data/CONTRIBUTING.md +122 -0
  12. data/Dockerfile +275 -0
  13. data/Gemfile +2 -0
  14. data/LICENSE +22 -0
  15. data/README.md +593 -0
  16. data/Rakefile +75 -0
  17. data/TODO.md +12 -0
  18. data/VERSION +1 -0
  19. data/bin/license_patrol +6 -0
  20. data/bin/license_patrol_pip.py +43 -0
  21. data/ci/pipelines/pull-request.yml.erb +138 -0
  22. data/ci/pipelines/release.yml.erb +176 -0
  23. data/ci/scripts/containerize-tests.sh +14 -0
  24. data/ci/scripts/pushscript.sh +31 -0
  25. data/ci/scripts/run-rubocop.sh +15 -0
  26. data/ci/scripts/run-tests.sh +41 -0
  27. data/ci/scripts/test.ps1 +81 -0
  28. data/ci/scripts/updateChangelog.sh +84 -0
  29. data/ci/tasks/build-and-push-gem.yml +10 -0
  30. data/ci/tasks/build-windows.yml +6 -0
  31. data/ci/tasks/build.yml +16 -0
  32. data/ci/tasks/rubocop.yml +15 -0
  33. data/ci/tasks/run-tests.yml +10 -0
  34. data/ci/tasks/update-changelog.yml +18 -0
  35. data/dlf +21 -0
  36. data/doc/dependency_decisions.yml +3 -0
  37. data/examples/Gemfile +4 -0
  38. data/examples/custom_erb_template.rb +24 -0
  39. data/examples/extract_license_data.rb +63 -0
  40. data/examples/sample_template.erb +7 -0
  41. data/lib/license_patrol/cli/approvals.rb +28 -0
  42. data/lib/license_patrol/cli/base.rb +113 -0
  43. data/lib/license_patrol/cli/dependencies.rb +44 -0
  44. data/lib/license_patrol/cli/ignored_dependencies.rb +32 -0
  45. data/lib/license_patrol/cli/ignored_groups.rb +32 -0
  46. data/lib/license_patrol/cli/inherited_decisions.rb +50 -0
  47. data/lib/license_patrol/cli/licenses.rb +31 -0
  48. data/lib/license_patrol/cli/main.rb +229 -0
  49. data/lib/license_patrol/cli/makes_decisions.rb +38 -0
  50. data/lib/license_patrol/cli/patched_thor.rb +33 -0
  51. data/lib/license_patrol/cli/permitted_licenses.rb +32 -0
  52. data/lib/license_patrol/cli/project_name.rb +32 -0
  53. data/lib/license_patrol/cli/restricted_licenses.rb +32 -0
  54. data/lib/license_patrol/cli.rb +20 -0
  55. data/lib/license_patrol/configuration.rb +197 -0
  56. data/lib/license_patrol/core.rb +121 -0
  57. data/lib/license_patrol/decision_applier.rb +70 -0
  58. data/lib/license_patrol/decisions.rb +334 -0
  59. data/lib/license_patrol/decisions_factory.rb +13 -0
  60. data/lib/license_patrol/diff.rb +51 -0
  61. data/lib/license_patrol/license/any_matcher.rb +15 -0
  62. data/lib/license_patrol/license/definitions.rb +498 -0
  63. data/lib/license_patrol/license/header_matcher.rb +17 -0
  64. data/lib/license_patrol/license/matcher.rb +24 -0
  65. data/lib/license_patrol/license/none_matcher.rb +11 -0
  66. data/lib/license_patrol/license/template.rb +19 -0
  67. data/lib/license_patrol/license/templates/0BSD.txt +10 -0
  68. data/lib/license_patrol/license/templates/AGPL3.txt +661 -0
  69. data/lib/license_patrol/license/templates/Apache1_1.txt +16 -0
  70. data/lib/license_patrol/license/templates/Apache2.txt +170 -0
  71. data/lib/license_patrol/license/templates/Artistic.txt +128 -0
  72. data/lib/license_patrol/license/templates/BSD.txt +24 -0
  73. data/lib/license_patrol/license/templates/CC01.txt +30 -0
  74. data/lib/license_patrol/license/templates/CC01_alt.txt +31 -0
  75. data/lib/license_patrol/license/templates/CDDL1.txt +131 -0
  76. data/lib/license_patrol/license/templates/CDDL1_1.txt +123 -0
  77. data/lib/license_patrol/license/templates/CPL1.txt +217 -0
  78. data/lib/license_patrol/license/templates/EPL1.txt +86 -0
  79. data/lib/license_patrol/license/templates/EPL2.txt +80 -0
  80. data/lib/license_patrol/license/templates/GPLv2.txt +339 -0
  81. data/lib/license_patrol/license/templates/GPLv3.txt +674 -0
  82. data/lib/license_patrol/license/templates/ISC.txt +2 -0
  83. data/lib/license_patrol/license/templates/LGPL.txt +165 -0
  84. data/lib/license_patrol/license/templates/LGPL2_1.txt +169 -0
  85. data/lib/license_patrol/license/templates/MIT.txt +9 -0
  86. data/lib/license_patrol/license/templates/MPL1_1.txt +469 -0
  87. data/lib/license_patrol/license/templates/MPL2.txt +373 -0
  88. data/lib/license_patrol/license/templates/NewBSD.txt +21 -0
  89. data/lib/license_patrol/license/templates/OFL.txt +91 -0
  90. data/lib/license_patrol/license/templates/Python.txt +47 -0
  91. data/lib/license_patrol/license/templates/Ruby.txt +52 -0
  92. data/lib/license_patrol/license/templates/SimplifiedBSD.txt +19 -0
  93. data/lib/license_patrol/license/templates/Unlicense.txt +24 -0
  94. data/lib/license_patrol/license/templates/WTFPL.txt +14 -0
  95. data/lib/license_patrol/license/templates/Zlib.txt +17 -0
  96. data/lib/license_patrol/license/text.rb +49 -0
  97. data/lib/license_patrol/license.rb +123 -0
  98. data/lib/license_patrol/license_aggregator.rb +59 -0
  99. data/lib/license_patrol/logger.rb +69 -0
  100. data/lib/license_patrol/manual_licenses.rb +79 -0
  101. data/lib/license_patrol/package.rb +200 -0
  102. data/lib/license_patrol/package_delta.rb +61 -0
  103. data/lib/license_patrol/package_manager.rb +183 -0
  104. data/lib/license_patrol/package_managers/bower.rb +37 -0
  105. data/lib/license_patrol/package_managers/bundler.rb +134 -0
  106. data/lib/license_patrol/package_managers/cargo.rb +38 -0
  107. data/lib/license_patrol/package_managers/carthage.rb +68 -0
  108. data/lib/license_patrol/package_managers/cocoa_pods.rb +66 -0
  109. data/lib/license_patrol/package_managers/composer.rb +63 -0
  110. data/lib/license_patrol/package_managers/conan.rb +70 -0
  111. data/lib/license_patrol/package_managers/conda.rb +131 -0
  112. data/lib/license_patrol/package_managers/dep.rb +45 -0
  113. data/lib/license_patrol/package_managers/dotnet.rb +86 -0
  114. data/lib/license_patrol/package_managers/erlangmk.rb +50 -0
  115. data/lib/license_patrol/package_managers/glide.rb +42 -0
  116. data/lib/license_patrol/package_managers/go_15vendorexperiment.rb +87 -0
  117. data/lib/license_patrol/package_managers/go_dep.rb +80 -0
  118. data/lib/license_patrol/package_managers/go_modules.rb +93 -0
  119. data/lib/license_patrol/package_managers/go_workspace.rb +117 -0
  120. data/lib/license_patrol/package_managers/govendor.rb +73 -0
  121. data/lib/license_patrol/package_managers/gradle.rb +99 -0
  122. data/lib/license_patrol/package_managers/gvt.rb +69 -0
  123. data/lib/license_patrol/package_managers/maven.rb +69 -0
  124. data/lib/license_patrol/package_managers/mix.rb +131 -0
  125. data/lib/license_patrol/package_managers/npm.rb +70 -0
  126. data/lib/license_patrol/package_managers/nuget.rb +159 -0
  127. data/lib/license_patrol/package_managers/pip.rb +70 -0
  128. data/lib/license_patrol/package_managers/pipenv.rb +63 -0
  129. data/lib/license_patrol/package_managers/pnpm.rb +126 -0
  130. data/lib/license_patrol/package_managers/pub.rb +86 -0
  131. data/lib/license_patrol/package_managers/rebar.rb +65 -0
  132. data/lib/license_patrol/package_managers/sbt.rb +46 -0
  133. data/lib/license_patrol/package_managers/spm.rb +93 -0
  134. data/lib/license_patrol/package_managers/trash.rb +43 -0
  135. data/lib/license_patrol/package_managers/yarn.rb +195 -0
  136. data/lib/license_patrol/package_utils/activation.rb +40 -0
  137. data/lib/license_patrol/package_utils/conan_info_parser.rb +77 -0
  138. data/lib/license_patrol/package_utils/conan_info_parser_v2.rb +82 -0
  139. data/lib/license_patrol/package_utils/gradle_dependency_finder.rb +15 -0
  140. data/lib/license_patrol/package_utils/license_files.rb +51 -0
  141. data/lib/license_patrol/package_utils/licensing.rb +40 -0
  142. data/lib/license_patrol/package_utils/maven_dependency_finder.rb +57 -0
  143. data/lib/license_patrol/package_utils/notice_files.rb +51 -0
  144. data/lib/license_patrol/package_utils/possible_license_file.rb +33 -0
  145. data/lib/license_patrol/package_utils/pypi.rb +43 -0
  146. data/lib/license_patrol/package_utils/sbt_dependency_finder.rb +15 -0
  147. data/lib/license_patrol/packages/bower_package.rb +45 -0
  148. data/lib/license_patrol/packages/bundler_package.rb +33 -0
  149. data/lib/license_patrol/packages/cargo_package.rb +28 -0
  150. data/lib/license_patrol/packages/carthage_package.rb +18 -0
  151. data/lib/license_patrol/packages/cocoa_pods_package.rb +22 -0
  152. data/lib/license_patrol/packages/composer_package.rb +13 -0
  153. data/lib/license_patrol/packages/conan_package.rb +23 -0
  154. data/lib/license_patrol/packages/conda_package.rb +74 -0
  155. data/lib/license_patrol/packages/erlangmk_package.rb +112 -0
  156. data/lib/license_patrol/packages/go_package.rb +30 -0
  157. data/lib/license_patrol/packages/gradle_package.rb +30 -0
  158. data/lib/license_patrol/packages/manual_package.rb +27 -0
  159. data/lib/license_patrol/packages/maven_package.rb +39 -0
  160. data/lib/license_patrol/packages/merged_package.rb +44 -0
  161. data/lib/license_patrol/packages/mix_package.rb +13 -0
  162. data/lib/license_patrol/packages/npm_package.rb +219 -0
  163. data/lib/license_patrol/packages/nuget_package.rb +13 -0
  164. data/lib/license_patrol/packages/pip_package.rb +50 -0
  165. data/lib/license_patrol/packages/pnpm_package.rb +13 -0
  166. data/lib/license_patrol/packages/pubspec_package.rb +18 -0
  167. data/lib/license_patrol/packages/rebar_package.rb +13 -0
  168. data/lib/license_patrol/packages/sbt_package.rb +22 -0
  169. data/lib/license_patrol/packages/spm_package.rb +18 -0
  170. data/lib/license_patrol/packages/yarn_package.rb +13 -0
  171. data/lib/license_patrol/platform.rb +15 -0
  172. data/lib/license_patrol/printer.rb +39 -0
  173. data/lib/license_patrol/project_finder.rb +62 -0
  174. data/lib/license_patrol/report.rb +34 -0
  175. data/lib/license_patrol/reports/csv_report.rb +108 -0
  176. data/lib/license_patrol/reports/diff_report.rb +29 -0
  177. data/lib/license_patrol/reports/erb_report.rb +62 -0
  178. data/lib/license_patrol/reports/html_report.rb +13 -0
  179. data/lib/license_patrol/reports/json_report.rb +31 -0
  180. data/lib/license_patrol/reports/junit_report.rb +23 -0
  181. data/lib/license_patrol/reports/markdown_report.rb +9 -0
  182. data/lib/license_patrol/reports/merged_report.rb +16 -0
  183. data/lib/license_patrol/reports/templates/bootstrap.css +9 -0
  184. data/lib/license_patrol/reports/templates/html_report.erb +113 -0
  185. data/lib/license_patrol/reports/templates/junit_report.erb +41 -0
  186. data/lib/license_patrol/reports/templates/markdown_report.erb +49 -0
  187. data/lib/license_patrol/reports/templates/xml_report.erb +19 -0
  188. data/lib/license_patrol/reports/text_report.rb +12 -0
  189. data/lib/license_patrol/reports/xml_report.rb +23 -0
  190. data/lib/license_patrol/scanner.rb +83 -0
  191. data/lib/license_patrol/shared_helpers/cmd.rb +13 -0
  192. data/lib/license_patrol/shared_helpers/common_path.rb +29 -0
  193. data/lib/license_patrol/version.rb +6 -0
  194. data/lib/license_patrol.rb +14 -0
  195. data/license_patrol.gemspec +75 -0
  196. data/release/instructions.md +8 -0
  197. data/swift-all-keys.asc +240 -0
  198. metadata +615 -0
@@ -0,0 +1,183 @@
1
+ # frozen_string_literal: false
2
+
3
+ module LicensePatrol
4
+ # Super-class for the different package managers
5
+ # (Bundler, NPM, Pip, etc.)
6
+ #
7
+ # For guidance on adding a new package manager use the shared behavior
8
+ #
9
+ # it_behaves_like "a PackageManager"
10
+ #
11
+ # Additional guidelines are:
12
+ #
13
+ # - implement #current_packages, to return a list of `Package`s this package manager is tracking
14
+ # - implement #possible_package_paths, an array of `Pathname`s which are the possible locations which contain a configuration file/folder indicating the package manager is in use.
15
+ # - implement(Optional) #package_management_command, string for invoking the package manager
16
+ # - implement(Optional) #prepare_command, string for fetching dependencies for package manager (runs when the --prepare flag is passed to license_patrol)
17
+
18
+ class PackageManager
19
+ include LicensePatrol::SharedHelpers
20
+
21
+ class << self
22
+ def takes_priority_over
23
+ nil
24
+ end
25
+
26
+ def id
27
+ name.split('::').last.downcase
28
+ end
29
+ end
30
+
31
+ def installed?(logger = Core.default_logger)
32
+ if package_management_command.nil?
33
+ logger.debug self.class, 'no command defined' # TODO: comment me out
34
+ true
35
+ elsif command_exists?(package_management_command)
36
+ logger.debug self.class, 'is installed', color: :green
37
+ true
38
+ else
39
+ logger.info self.class, 'is not installed', color: :red
40
+ false
41
+ end
42
+ end
43
+
44
+ # see class description
45
+ def package_management_command
46
+ nil
47
+ end
48
+
49
+ # see class description
50
+ def prepare_command
51
+ nil
52
+ end
53
+
54
+ def command_exists?(command)
55
+ _stdout, _stderr, status =
56
+ if LicensePatrol::Platform.windows?
57
+ Cmd.run("where #{command}")
58
+ else
59
+ Cmd.run("which #{command}")
60
+ end
61
+
62
+ status.success?
63
+ end
64
+
65
+ def initialize(options = {})
66
+ @prepare_no_fail = options[:prepare_no_fail]
67
+ @logger = options[:logger] || Core.default_logger
68
+ @project_path = options[:project_path]
69
+ @log_directory = options[:log_directory]
70
+ @ignored_groups = options[:ignored_groups]
71
+ end
72
+
73
+ def active?
74
+ path = detected_package_path
75
+ path&.exist?
76
+ end
77
+
78
+ def project_root?
79
+ active?
80
+ end
81
+
82
+ def detected_package_path
83
+ possible_package_paths.find(&:exist?)
84
+ end
85
+
86
+ def prepare
87
+ if prepare_command
88
+ stdout, stderr, status = Dir.chdir(project_path) { Cmd.run(prepare_command) }
89
+ unless status.success?
90
+ log_errors stderr
91
+
92
+ error_message = "Prepare command '#{prepare_command}' failed\n#{stderr}"
93
+
94
+ error_message == error_message.concat("\n#{stdout}\n") if !stdout.nil? && !stdout.empty?
95
+
96
+ raise error_message unless @prepare_no_fail
97
+ end
98
+ else
99
+ logger.debug self.class, 'no prepare step provided', color: :red
100
+ end
101
+ end
102
+
103
+ def current_packages_with_relations
104
+ begin
105
+ packages = current_packages
106
+ rescue StandardError => e
107
+ raise e unless @prepare_no_fail
108
+
109
+ packages = []
110
+ end
111
+
112
+ packages.each do |parent|
113
+ parent.children.each do |child_name|
114
+ child = packages.detect { |child_package| child_package.name == child_name }
115
+ child.parents << parent.name if child
116
+ end
117
+ end
118
+ packages
119
+ end
120
+
121
+ private
122
+
123
+ attr_reader :logger, :project_path
124
+
125
+ def log_errors(stderr)
126
+ log_errors_with_cmd(prepare_command, stderr)
127
+ end
128
+
129
+ def log_errors_with_cmd(prep_cmd, stderr)
130
+ logger.info(prep_cmd, 'did not succeed.', color: :red)
131
+ logger.info(prep_cmd, stderr, color: :red)
132
+ log_to_file(prep_cmd, stderr)
133
+ end
134
+
135
+ def log_to_file(prep_cmd, contents)
136
+ FileUtils.mkdir_p @log_directory
137
+
138
+ # replace whitespace with underscores and remove slashes
139
+ log_file_name = package_management_command&.gsub(/\s/, '_')&.delete('/')
140
+ log_file = File.join(@log_directory, "prepare_#{log_file_name || 'errors'}.log")
141
+
142
+ File.open(log_file, 'w') do |f|
143
+ f.write("Prepare command \"#{prep_cmd}\" failed with:\n")
144
+ f.write("#{contents}\n\n")
145
+ end
146
+ end
147
+ end
148
+ end
149
+
150
+ require 'license_patrol/package'
151
+
152
+ require 'license_patrol/package_managers/bower'
153
+ require 'license_patrol/package_managers/go_workspace'
154
+ require 'license_patrol/package_managers/go_15vendorexperiment'
155
+ require 'license_patrol/package_managers/go_dep'
156
+ require 'license_patrol/package_managers/gvt'
157
+ require 'license_patrol/package_managers/glide'
158
+ require 'license_patrol/package_managers/govendor'
159
+ require 'license_patrol/package_managers/go_modules'
160
+ require 'license_patrol/package_managers/trash'
161
+ require 'license_patrol/package_managers/bundler'
162
+ require 'license_patrol/package_managers/npm'
163
+ require 'license_patrol/package_managers/pnpm'
164
+ require 'license_patrol/package_managers/yarn'
165
+ require 'license_patrol/package_managers/pip'
166
+ require 'license_patrol/package_managers/pipenv'
167
+ require 'license_patrol/package_managers/maven'
168
+ require 'license_patrol/package_managers/mix'
169
+ require 'license_patrol/package_managers/cocoa_pods'
170
+ require 'license_patrol/package_managers/carthage'
171
+ require 'license_patrol/package_managers/spm'
172
+ require 'license_patrol/package_managers/gradle'
173
+ require 'license_patrol/package_managers/rebar'
174
+ require 'license_patrol/package_managers/erlangmk'
175
+ require 'license_patrol/package_managers/nuget'
176
+ require 'license_patrol/package_managers/dotnet'
177
+ require 'license_patrol/package_managers/dep'
178
+ require 'license_patrol/package_managers/conan'
179
+ require 'license_patrol/package_managers/sbt'
180
+ require 'license_patrol/package_managers/cargo'
181
+ require 'license_patrol/package_managers/composer'
182
+ require 'license_patrol/package_managers/conda'
183
+ require 'license_patrol/package_managers/pub'
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module LicensePatrol
6
+ class Bower < PackageManager
7
+ def current_packages
8
+ bower_output.map do |package|
9
+ BowerPackage.new(package, logger: logger)
10
+ end
11
+ end
12
+
13
+ def package_management_command
14
+ 'bower'
15
+ end
16
+
17
+ def prepare_command
18
+ 'bower install'
19
+ end
20
+
21
+ def possible_package_paths
22
+ [project_path.join('bower.json')]
23
+ end
24
+
25
+ private
26
+
27
+ def bower_output
28
+ command = "#{package_management_command} list --json -l action --allow-root"
29
+ stdout, stderr, status = Dir.chdir(project_path) { Cmd.run(command) }
30
+ raise "Command '#{command}' failed to execute: #{stderr}" unless status.success?
31
+
32
+ JSON(stdout)
33
+ .fetch('dependencies', {})
34
+ .values
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler'
4
+ require 'securerandom'
5
+
6
+ module LicensePatrol
7
+ class Bundler < PackageManager
8
+ def initialize(options = {})
9
+ super
10
+ @ignored_groups = options[:ignored_groups]
11
+ @definition = options[:definition] # dependency injection for tests
12
+ end
13
+
14
+ def current_packages
15
+ with_gemfile do
16
+ logger.debug self.class, "including groups #{included_groups.inspect}"
17
+ details.map do |gem_detail, bundle_detail|
18
+ BundlerPackage.new(gem_detail, bundle_detail, logger: logger).tap do |package|
19
+ log_package_dependencies package
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ def package_management_command
26
+ 'bundle'
27
+ end
28
+
29
+ def prepare_command
30
+ ignored_groups_argument = !ignored_groups.empty? ? "--without #{ignored_groups.to_a.join(' ')}" : ''
31
+
32
+ gem_path = "lf-bundler-gems-#{SecureRandom.uuid}"
33
+ logger.info self.class, "Running bundle install for #{Dir.pwd} with path #{gem_path}", color: :blue
34
+
35
+ "bundle install #{ignored_groups_argument} --path #{gem_path}".strip
36
+ end
37
+
38
+ def possible_package_paths
39
+ [gemfile_path]
40
+ end
41
+
42
+ private
43
+
44
+ attr_reader :ignored_groups
45
+
46
+ def definition
47
+ return @definition if @definition
48
+
49
+ @definition = ::Bundler::Definition.build(detected_package_path, lockfile_path, nil) if detected_package_path
50
+ end
51
+
52
+ def with_gemfile
53
+ previous_gemfile = ENV['BUNDLE_GEMFILE']
54
+ ENV['BUNDLE_GEMFILE'] = gemfile_path.to_s
55
+ yield
56
+ ensure
57
+ ENV['BUNDLE_GEMFILE'] = previous_gemfile
58
+ end
59
+
60
+ def details
61
+ gem_details.map do |gem_detail|
62
+ bundle_detail = bundler_details.detect { |bundler_detail| bundler_detail.name == gem_detail.name }
63
+ [gem_detail, bundle_detail]
64
+ end
65
+ end
66
+
67
+ def gem_details
68
+ return @gem_details if @gem_details
69
+
70
+ # clear gem paths before running specs_for
71
+ Gem.clear_paths
72
+ if bundler_config_path_found
73
+ ::Bundler.reset!
74
+ ::Bundler.configure
75
+ end
76
+ @gem_details = definition&.specs_for(included_groups) || []
77
+ end
78
+
79
+ def bundler_details
80
+ @bundler_details ||= definition&.dependencies || []
81
+ end
82
+
83
+ def included_groups
84
+ definition_groups - ignored_groups.map(&:to_sym)
85
+ end
86
+
87
+ def definition_groups
88
+ definition&.groups || []
89
+ end
90
+
91
+ def lockfile_path
92
+ Pathname("#{gemfile_path}.lock")
93
+ end
94
+
95
+ # Bundler keeps the config (e.g. BUNDLE_PATH for vendored gems) next to the gemfile
96
+ def bundler_config_path_found
97
+ config_file = gemfile_path.dirname.join('.bundle/config')
98
+
99
+ return false unless File.exist?(config_file)
100
+
101
+ content = File.readlines(config_file)
102
+ content.grep(/BUNDLE_PATH/).count.positive?
103
+ end
104
+
105
+ def log_package_dependencies(package)
106
+ dependencies = package.children
107
+ if dependencies.empty?
108
+ logger.debug self.class, format("package '%s' has no dependencies", package.name)
109
+ else
110
+ logger.debug self.class, format("package '%s' has dependencies:", package.name)
111
+ dependencies.each do |dep|
112
+ logger.debug self.class, format('- %s', dep)
113
+ end
114
+ end
115
+ end
116
+
117
+ def gemfile_path
118
+ @gemfile_path ||= project_path.join(gemfile)
119
+ end
120
+
121
+ # BUNDLE_GEMFILE is taken as relative to the project path. Through `bundle exec`
122
+ # it gets expanded to an absolute path, which may point to another project
123
+ # (e.g. when using --project_path), in which case only its basename is kept.
124
+ def gemfile
125
+ gemfile = ENV['BUNDLE_GEMFILE']
126
+ return 'Gemfile' if gemfile.nil? || gemfile.empty?
127
+ return gemfile unless Pathname(gemfile).absolute?
128
+
129
+ gemfile = Pathname(gemfile).expand_path
130
+ project_root = project_path.expand_path
131
+ gemfile.ascend.any? { |path| path == project_root } ? gemfile.to_s : gemfile.basename.to_s
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module LicensePatrol
6
+ class Cargo < PackageManager
7
+ def current_packages
8
+ cargo_output.map do |package|
9
+ path = Dir.glob("#{Dir.home}/.cargo/registry/src/*/#{package['name']}-#{package['version']}").first
10
+ CargoPackage.new(package, logger: logger, install_path: path)
11
+ end
12
+ end
13
+
14
+ def package_management_command
15
+ 'cargo'
16
+ end
17
+
18
+ def prepare_command
19
+ 'cargo fetch'
20
+ end
21
+
22
+ def possible_package_paths
23
+ [project_path.join('Cargo.lock'), project_path.join('Cargo.toml')]
24
+ end
25
+
26
+ private
27
+
28
+ def cargo_output
29
+ command = "#{package_management_command} metadata --format-version=1"
30
+
31
+ stdout, stderr, status = Dir.chdir(project_path) { Cmd.run(command) }
32
+ raise "Command '#{command}' failed to execute: #{stderr}" unless status.success?
33
+
34
+ JSON(stdout)
35
+ .fetch('packages', [])
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module LicensePatrol
6
+ class Carthage < PackageManager
7
+ class CarthageError < RuntimeError; end
8
+
9
+ def current_packages
10
+ cartfile.each_line.map do |line|
11
+ name, version = name_version_from_line line
12
+
13
+ CarthagePackage.new(
14
+ name,
15
+ version,
16
+ license_text(name),
17
+ logger: logger,
18
+ install_path: project_checkout(name)
19
+ )
20
+ end
21
+ end
22
+
23
+ def package_management_command
24
+ LicensePatrol::Platform.darwin? ? 'carthage' : nil
25
+ end
26
+
27
+ def possible_package_paths
28
+ [public_dependency_path]
29
+ end
30
+
31
+ private
32
+
33
+ def cartfile
34
+ if File.exist?(resolved_path)
35
+ @cartfile ||= IO.read(resolved_path)
36
+ else
37
+ raise CarthageError, 'No Cartfile.resolved found.
38
+ Please install your dependencies first.'
39
+ end
40
+ end
41
+
42
+ def public_dependency_path
43
+ project_path.join('Cartfile')
44
+ end
45
+
46
+ def resolved_path
47
+ project_path.join('Cartfile.resolved')
48
+ end
49
+
50
+ def project_checkout(name)
51
+ project_path.join('Carthage', 'Checkouts', name)
52
+ end
53
+
54
+ def license_text(name)
55
+ license_path = license_pattern(name).find { |f| File.exist?(f) }
56
+ license_path.nil? ? nil : IO.read(license_path)
57
+ end
58
+
59
+ def license_pattern(name)
60
+ checkout_path = project_checkout(name)
61
+ Dir.glob(checkout_path.join('LICENSE*'), File::FNM_CASEFOLD)
62
+ end
63
+
64
+ def name_version_from_line(cartfile_line)
65
+ cartfile_line.split(' ')[1, 2].map { |f| f.split('/').last.delete('"').gsub('.git', '') }
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'open3'
5
+
6
+ module LicensePatrol
7
+ class CocoaPods < PackageManager
8
+ def current_packages
9
+ podfile = YAML.load_file(lockfile_path)
10
+
11
+ podfile['PODS'].map do |pod|
12
+ pod = pod.keys.first if pod.is_a?(Hash)
13
+
14
+ name, version = pod.scan(/(.*)\s\((.*)\)/).flatten
15
+
16
+ CocoaPodsPackage.new(
17
+ name,
18
+ version,
19
+ license_texts[name],
20
+ logger: logger
21
+ )
22
+ end
23
+ end
24
+
25
+ def package_management_command
26
+ LicensePatrol::Platform.darwin? ? 'pod' : nil
27
+ end
28
+
29
+ def possible_package_paths
30
+ [project_path.join('Podfile')]
31
+ end
32
+
33
+ private
34
+
35
+ def lockfile_path
36
+ project_path.join('Podfile.lock')
37
+ end
38
+
39
+ def license_texts
40
+ # package name => license text
41
+ @license_texts ||= read_plist(acknowledgements_path)['PreferenceSpecifiers']
42
+ .each_with_object({}) { |hash, memo| memo[hash['Title']] = hash['FooterText'] }
43
+ end
44
+
45
+ def acknowledgements_path
46
+ if !ENV['ACKNOWLEDGEMENTS_PATH'].nil?
47
+ result = Dir[*ENV['ACKNOWLEDGEMENTS_PATH']].first
48
+ else
49
+ search_paths = ['Pods/Pods-acknowledgements.plist',
50
+ 'Pods/Target Support Files/Pods/Pods-acknowledgements.plist',
51
+ 'Pods/Target Support Files/Pods-*/Pods-*-acknowledgements.plist']
52
+
53
+ result = Dir[*search_paths.map { |path| File.join(project_path, path) }].first
54
+ raise "Found a Podfile but no Pods directory in #{project_path}. Try running pod install before running license_patrol." if result.nil?
55
+ end
56
+ result
57
+ end
58
+
59
+ def read_plist(pathname)
60
+ out, err, status = Open3.capture3('plutil', '-convert', 'json', '-o', '-', pathname)
61
+ raise "#{out}\n\n#{err}" unless status.success?
62
+
63
+ JSON.parse(out)
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module LicensePatrol
6
+ class Composer < PackageManager
7
+ def initialize(options = {})
8
+ super
9
+ @check_require_only = !!options[:composer_check_require_only]
10
+ end
11
+
12
+ def possible_package_paths
13
+ [project_path.join('composer.lock'), project_path.join('composer.json')]
14
+ end
15
+
16
+ def current_packages
17
+ dependency_list.map do |name, dependency|
18
+ path_command = "composer show #{name} -P"
19
+ stdout, _stderr, status = Dir.chdir(project_path) { Cmd.run(path_command) }
20
+
21
+ path = status.success? ? stdout.split(' ').last : ''
22
+ ComposerPackage.new(name, dependency['version'], spec_licenses: dependency['license'], install_path: path)
23
+ end
24
+ end
25
+
26
+ def prepare
27
+ _stdout, stderr, status = Dir.chdir(project_path) { Cmd.run(prepare_command) }
28
+ return if status.success?
29
+
30
+ log_errors stderr
31
+ raise "Prepare command '#{Composer.prepare_command}' failed" unless @prepare_no_fail
32
+ end
33
+
34
+ def package_management_command
35
+ 'composer'
36
+ end
37
+
38
+ def prepare_command
39
+ 'composer install --no-plugins --no-scripts --ignore-platform-reqs --no-interaction'
40
+ end
41
+
42
+ def package_path
43
+ project_path.join('composer.json')
44
+ end
45
+
46
+ def lockfile_path
47
+ project_path.join('composer.lock')
48
+ end
49
+
50
+ def dependency_list
51
+ json ||= composer_json
52
+ json.fetch('dependencies', {}).reject { |_, d| d.is_a?(String) }
53
+ end
54
+
55
+ def composer_json
56
+ command = "composer licenses --format=json#{@check_require_only ? ' --no-dev' : ''}"
57
+ stdout, stderr, status = Dir.chdir(project_path) { Cmd.run(command) }
58
+ raise "Command '#{command}' failed to execute: #{stderr}" unless status.success?
59
+
60
+ JSON(stdout)
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'license_patrol/package_utils/conan_info_parser'
4
+ require 'license_patrol/package_utils/conan_info_parser_v2'
5
+
6
+ module LicensePatrol
7
+ class Conan < PackageManager
8
+ def possible_package_paths
9
+ [project_path.join('conanfile.txt'), project_path.join('conanfile.py')]
10
+ end
11
+
12
+ def license_file_is_good?(license_file_path)
13
+ !license_file_path.nil? && File.file?(license_file_path)
14
+ end
15
+
16
+ def license_file(project_path, name)
17
+ candidates = Dir.glob("#{project_path}/licenses/#{name}/**/LICENSE*")
18
+ candidates.each do |candidate|
19
+ return candidate if license_file_is_good?(candidate)
20
+ end
21
+ nil
22
+ end
23
+
24
+ def deps_list_conan_v1(project_path)
25
+ info_command = 'conan info .'
26
+ info_output, _stderr, _status = Dir.chdir(project_path) { Cmd.run(info_command) }
27
+ return nil if info_output.empty?
28
+
29
+ info_parser = ConanInfoParser.new
30
+ info_parser.parse(info_output)
31
+ end
32
+
33
+ def deps_list_conan_v2(project_path)
34
+ info_command = 'conan graph info .'
35
+ info_output, stderr, _status = Dir.chdir(project_path) { Cmd.run(info_command) }
36
+ if info_output.empty?
37
+ return if stderr.empty?
38
+
39
+ info_output = stderr
40
+ end
41
+ info_parser = ConanInfoParserV2.new
42
+ info_parser.parse(info_output)
43
+ end
44
+
45
+ def deps_list(project_path)
46
+ deps = deps_list_conan_v1(project_path)
47
+ deps = deps_list_conan_v2(project_path) if deps.nil? || deps.empty?
48
+ deps
49
+ end
50
+
51
+ def current_packages
52
+ install_command = 'conan install .'
53
+ Dir.chdir(project_path) { Cmd.run(install_command) }
54
+
55
+ deps = deps_list(project_path)
56
+ return [] if deps.nil?
57
+
58
+ deps.map do |dep|
59
+ name, version = dep['name'].split('/')
60
+ license_file_path = license_file(project_path, name)
61
+
62
+ next unless license_file_is_good?(license_file_path)
63
+
64
+ url = dep['homepage']
65
+ url = dep['url'] if url.nil?
66
+ ConanPackage.new(name, version, File.open(license_file_path).read, url)
67
+ end.compact
68
+ end
69
+ end
70
+ end