diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8aef28f..a31c147 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,31 +1,27 @@ -name: Ruby +name: jar-dependencies -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] +on: [push, pull_request] jobs: test: - runs-on: ubuntu-latest strategy: + fail-fast: false matrix: - ruby-version: [jruby-9.3, jruby-9.4, jruby-10.0] + ruby-version: [jruby-9.4, jruby-10.0, jruby-head] steps: - - uses: actions/checkout@v5 - - name: Set up Ruby + - uses: actions/checkout@v6 + - name: Set up JRuby uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby-version }} bundler-cache: true - - name: Install dependencies - run: bundle install - - name: Run test - run: jruby -Ilib -rbundler/setup -S rake specs + - name: Download Mima jars + run: bundle exec rake download_jars + - name: Run tests + run: bundle exec rake specs - name: Run RuboCop if: matrix.ruby-version == 'jruby-10.0' # Only run RuboCop on modern JRuby; target older jrubies via .rubocop.yml - run: jruby -Ilib -rbundler/setup -S rubocop lib specs + run: bundle exec rubocop lib diff --git a/.gitignore b/.gitignore index abf24fc..001f426 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ build.log pom* specs/repository/* example/dependencies.list +lib/jars/mima/*.jar diff --git a/Gemfile b/Gemfile index 470ea63..04be930 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,6 @@ # frozen_string_literal: true -source 'https://rubygems.org' +source 'https://gem.coop' gemspec diff --git a/Rakefile b/Rakefile index 288002a..383cd83 100644 --- a/Rakefile +++ b/Rakefile @@ -4,6 +4,7 @@ task default: [:specs] require 'bundler/gem_tasks' require 'rubocop/rake_task' +require 'rake/clean' RuboCop::RakeTask.new @@ -15,3 +16,47 @@ task :specs do require File.basename(f.sub(/.rb$/, '')) end end + +require_relative 'lib/jars/mima/version' + +MIMA_VERSION = Jars::Mima::MIMA_VERSION +SLF4J_VERSION = Jars::Mima::SLF4J_VERSION +MAVEN_CENTRAL = 'https://repo.maven.apache.org/maven2' +MIMA_DIR = 'lib/jars/mima' + +MIMA_JARS = { + "slf4j-api-#{SLF4J_VERSION}.jar" => + "#{MAVEN_CENTRAL}/org/slf4j/slf4j-api/#{SLF4J_VERSION}/slf4j-api-#{SLF4J_VERSION}.jar", + "slf4j-simple-#{SLF4J_VERSION}.jar" => + "#{MAVEN_CENTRAL}/org/slf4j/slf4j-simple/#{SLF4J_VERSION}/slf4j-simple-#{SLF4J_VERSION}.jar", + "jcl-over-slf4j-#{SLF4J_VERSION}.jar" => + "#{MAVEN_CENTRAL}/org/slf4j/jcl-over-slf4j/#{SLF4J_VERSION}/jcl-over-slf4j-#{SLF4J_VERSION}.jar", + "context-#{MIMA_VERSION}.jar" => + "#{MAVEN_CENTRAL}/eu/maveniverse/maven/mima/context/#{MIMA_VERSION}/context-#{MIMA_VERSION}.jar", + "standalone-static-uber-#{MIMA_VERSION}.jar" => + "#{MAVEN_CENTRAL}/eu/maveniverse/maven/mima/runtime/standalone-static-uber/#{MIMA_VERSION}/standalone-static-uber-#{MIMA_VERSION}.jar" +} + +MIMA_JARS.each_key { |jar| CLEAN.include(File.join(MIMA_DIR, jar)) } + +desc 'download Mima (and dependent SLF4J) jars' +task :download_jars do + require 'fileutils' + require 'open-uri' + + FileUtils.mkdir_p(MIMA_DIR) + + MIMA_JARS.each do |filename, url| + target = File.join(MIMA_DIR, filename) + if File.exist?(target) + puts " exists: #{target}" + next + end + + puts " downloading #{filename}..." + URI.open(url) do |remote| # rubocop:disable Security/Open + File.open(target, 'wb') { |f| f.write(remote.read) } + end + puts " saved: #{target}" + end +end diff --git a/jar-dependencies.gemspec b/jar-dependencies.gemspec index fe2f0db..b7bfc2a 100644 --- a/jar-dependencies.gemspec +++ b/jar-dependencies.gemspec @@ -13,7 +13,7 @@ Gem::Specification.new do |s| s.homepage = 'https://github.com/jruby/jar-dependencies' s.bindir = 'exe' - s.executables = [lock_jars = 'lock_jars'] + s.executables = ['lock_jars'] s.license = 'MIT' @@ -30,18 +30,6 @@ Gem::Specification.new do |s| s.required_ruby_version = '>= 2.6' s.add_development_dependency 'minitest', '~> 5.10' - s.add_development_dependency 'ruby-maven', ruby_maven_version = '~> 3.9' - s.post_install_message = <<~TEXT - - if you want to use the executable #{lock_jars} then install ruby-maven gem before using #{lock_jars} - - $ gem install ruby-maven -v '#{ruby_maven_version}' - - or add it as a development dependency to your Gemfile - - gem 'ruby-maven', '#{ruby_maven_version}' - - TEXT s.metadata['rubygems_mfa_required'] = 'true' end diff --git a/lib/jar_dependencies.rb b/lib/jar_dependencies.rb index 54232c7..212b6e4 100644 --- a/lib/jar_dependencies.rb +++ b/lib/jar_dependencies.rb @@ -51,8 +51,9 @@ module Jars UNKNOWN = 'unknown' end - autoload :MavenSettings, 'jars/maven_settings' autoload :Classpath, 'jars/classpath' + autoload :MavenSettings, 'jars/maven_settings' + autoload :Mima, 'jars/mima' @jars_lock = false @jars = {} @@ -144,12 +145,7 @@ def lock def jars_lock_from_class_loader return unless defined?(JRUBY_VERSION) - if JRuby::Util.respond_to?(:class_loader_resources) - JRuby::Util.class_loader_resources('Jars.lock') - else - require 'jruby' - JRuby.runtime.jruby_class_loader.get_resources('Jars.lock').collect(&:to_s) - end + JRuby::Util.class_loader_resources('Jars.lock') end def lock_path(basedir = nil) diff --git a/lib/jars/attach_jars_pom.rb b/lib/jars/attach_jars_pom.rb deleted file mode 100644 index d08ba36..0000000 --- a/lib/jars/attach_jars_pom.rb +++ /dev/null @@ -1,28 +0,0 @@ -# frozen_string_literal: true - -# this file is maven DSL - -10_000.times do |i| - coord = ENV_JAVA["jars.#{i}"] - break unless coord - - artifact = Maven::Tools::Artifact.from_coordinate(coord) - exclusions = [] - 10_000.times do |j| - exclusion = ENV_JAVA["jars.#{i}.exclusions.#{j}"] - break unless exclusion - - exclusions << exclusion - end - scope = ENV_JAVA["jars.#{i}.scope"] - artifact.scope = scope if scope - classifier = ENV_JAVA["jars.#{i}.classifier"] - artifact.classifier = classifier if classifier - - # declare the artifact inside the POM - dependency_artifact(artifact) do - exclusions.each do |ex| - exclusion ex - end - end -end diff --git a/lib/jars/gemspec_pom.rb b/lib/jars/gemspec_pom.rb deleted file mode 100644 index d997623..0000000 --- a/lib/jars/gemspec_pom.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -# this file is maven DSL and used by maven via jars/maven_exec.rb - -def eval_file(file) - file = File.join(__dir__, file) - eval(File.read(file), nil, file) # rubocop:disable Security/Eval -end - -eval_file('attach_jars_pom.rb') -eval_file('output_jars_pom.rb') diff --git a/lib/jars/installer.rb b/lib/jars/installer.rb index 4f73d6f..6dd6c78 100644 --- a/lib/jars/installer.rb +++ b/lib/jars/installer.rb @@ -171,7 +171,7 @@ def install_jars(write_require_file: true) end def ruby_maven_install_options=(options) - @mvn.ruby_maven_install_options = options + # no-op: kept for backward compatibility with post_install_hook end def jars? diff --git a/lib/jars/lock_down.rb b/lib/jars/lock_down.rb index 4e9ebbb..d04317d 100644 --- a/lib/jars/lock_down.rb +++ b/lib/jars/lock_down.rb @@ -3,7 +3,6 @@ require 'fileutils' require 'jar_dependencies' require 'jars/version' -require 'jars/maven_factory' require 'jars/gemspec_artifacts' module Jars @@ -15,38 +14,49 @@ def initialize(debug, verbose) @verbose = verbose end - def maven_new - factory = MavenFactory.new({}, @debug, @verbose) - pom = File.expand_path('lock_down_pom.rb', __dir__) - m = factory.maven_new(pom) - m['jruby.plugins.version'] = Jars::JRUBY_PLUGINS_VERSION - m['dependency.plugin.version'] = Jars::DEPENDENCY_PLUGIN_VERSION - m['jars.basedir'] = File.expand_path(basedir) - jarfile = File.expand_path(Jars.jarfile) - m['jars.jarfile'] = jarfile if File.exist?(jarfile) - attach_jar_coordinates_from_bundler_dependencies(m) - m + def basedir + File.expand_path('.') end - private :maven_new - def maven - @maven ||= maven_new - end + def collect_artifacts + artifacts = [] + done = [] - def basedir - File.expand_path('.') + attach_jar_coordinates_from_bundler_dependencies(artifacts, done) + + # Also collect from local gemspec if present + specs = Dir['*.gemspec'] + if specs.size == 1 + spec = eval(File.read(specs.first), TOPLEVEL_BINDING, specs.first) # rubocop:disable Security/Eval + ga = GemspecArtifacts.new(spec) + ga.artifacts.each do |a| + unless done.include?(a.key) + artifacts << a + done << a.key + end + end + end + + artifacts end + private :collect_artifacts - def attach_jar_coordinates_from_bundler_dependencies(maven) + def attach_jar_coordinates_from_bundler_dependencies(artifacts, done) load_path = $LOAD_PATH.dup require 'bundler' # TODO: make this group a commandline option Bundler.setup('default') - maven.property('jars.bundler', true) cwd = File.expand_path('.') Gem.loaded_specs.each_value do |spec| - # if gemspec is local then include all dependencies - maven.attach_jars(spec, all_dependencies: cwd == spec.full_gem_path) + all = cwd == spec.full_gem_path # if gemspec is local then include all dependencies + ga = GemspecArtifacts.new(spec) + ga.artifacts.each do |a| + next if done.include?(a.key) + next unless all || (a.scope != 'provided' && a.scope != 'test') + + artifacts << a + done << a.key + end end rescue LoadError => e if Jars.verbose? @@ -62,40 +72,73 @@ def attach_jar_coordinates_from_bundler_dependencies(maven) $LOAD_PATH.replace(load_path) end - def lock_down(vendor_dir = nil, force: false, update: false, tree: nil) - out = File.expand_path('.jars.output') - tree_provided = tree - tree ||= File.expand_path('.jars.tree') - maven.property('jars.outputFile', out) - maven.property('maven.repo.local', Jars.local_maven_repo) - maven.property('jars.home', File.expand_path(vendor_dir)) if vendor_dir - maven.property('jars.lock', File.expand_path(Jars.lock)) - maven.property('jars.force', force) - maven.property('jars.update', update) if update - # tell not to use Jars.lock as part of POM when running mvn - maven.property('jars.skip.lock', true) - - args = ['gem:jars-lock'] - args += ['dependency:tree', '-P -gemfile.lock', "-DoutputFile=#{tree}"] if tree_provided + def lock_down(vendor_dir = nil, force: false, update: false, tree: nil) # rubocop:disable Lint/UnusedMethodArgument + require 'jars/mima' + + lock_file = File.expand_path(Jars.lock) + + if !force && File.exist?(lock_file) + puts 'Jars.lock already exists, use --force to overwrite' + return + end + + artifacts = collect_artifacts + + if artifacts.empty? + puts 'no jar dependencies found' + return + end puts puts '-- jar root dependencies --' puts - status = maven.exec(*args) - exit 1 unless status - if File.exist?(tree) + artifacts.each do |a| + puts " #{a.to_gacv}:#{a.scope || 'compile'}" + puts " exclusions: #{a.exclusions}" if a.exclusions && !a.exclusions.empty? + end + + context = Jars::Mima.create_context + begin + resolved = Jars::Mima.resolve_with_context(context, artifacts, all_dependencies: true) + ensure + context.close + end + + # Write Jars.lock + File.open(lock_file, 'w') do |f| + resolved.each do |dep| + next unless dep.type == 'jar' + + f.puts dep.to_lock_entry + end + end + + # Optionally vendor jars + if vendor_dir + vendor_path = File.expand_path(vendor_dir) + resolved.each do |dep| + next unless dep.type == 'jar' && dep.runtime? && !dep.system? + + target = File.join(vendor_path, dep.jar_path) + FileUtils.mkdir_p(File.dirname(target)) + FileUtils.cp(dep.file, target) + end + end + + if tree puts puts '-- jar dependency tree --' puts - puts File.read(tree) + resolved.each do |dep| + prefix = dep.classifier ? "#{dep.classifier}:" : '' + puts " #{dep.group_id}:#{dep.artifact_id}:#{prefix}#{dep.version}:#{dep.scope}" + end puts end + puts - puts File.read(out).gsub("#{File.dirname(out)}/", '') + puts File.read(lock_file) puts - ensure - FileUtils.rm_f out - FileUtils.rm_f tree end end end diff --git a/lib/jars/lock_down_pom.rb b/lib/jars/lock_down_pom.rb deleted file mode 100644 index 3dffa35..0000000 --- a/lib/jars/lock_down_pom.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -# this file is maven DSL and used by maven via jars/lock_down.rb - -basedir(ENV_JAVA['jars.basedir']) - -def eval_file(file) - file = File.join(__dir__, file) - eval(File.read(file), nil, file) # rubocop:disable Security/Eval -end - -eval_file('attach_jars_pom.rb') - -jfile = ENV_JAVA['jars.jarfile'] -jarfile(jfile) if jfile - -# need to fix the version of this plugin for gem:jars_lock goal -jruby_plugin :gem, ENV_JAVA['jruby.plugins.version'] - -# if you use bundler we collect all root jar dependencies -# from each gemspec file. otherwise we need to resolve -# the gemspec artifact in the maven way -unless ENV_JAVA['jars.bundler'] - begin - gemspec - rescue - nil - end -end - -properties('project.build.sourceEncoding' => 'utf-8') - -plugin :dependency, ENV_JAVA['dependency.plugin.version'] - -eval_file('output_jars_pom.rb') diff --git a/lib/jars/maven_exec.rb b/lib/jars/maven_exec.rb index 798c018..db82755 100644 --- a/lib/jars/maven_exec.rb +++ b/lib/jars/maven_exec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'jar_dependencies' -require 'jars/maven_factory' +require 'jars/gemspec_artifacts' module Jars class MavenExec @@ -65,21 +65,27 @@ def ruby_maven_install_options=(options) end def resolve_dependencies_list(file) - factory = MavenFactory.new(@options) - maven = factory.maven_new(File.expand_path('gemspec_pom.rb', __dir__)) + require 'jars/mima' + artifacts = GemspecArtifacts.new(@spec) is_local_file = File.expand_path(File.dirname(@specfile)) == File.expand_path(Dir.pwd) - maven.attach_jars(@spec, all_dependencies: is_local_file) - maven['jars.specfile'] = @specfile.to_s - maven['outputAbsoluteArtifactFilename'] = 'true' - maven['includeTypes'] = 'jar' - maven['outputScope'] = 'true' - maven['useRepositoryLayout'] = 'true' - maven['outputDirectory'] = Jars.home.to_s - maven['outputFile'] = file.to_s + resolved = Jars::Mima.resolve_artifacts(artifacts.artifacts, all_dependencies: is_local_file) - maven.exec('dependency:copy-dependencies', 'dependency:list') + # Write output in Maven dependency:list format for Installer::Dependency compatibility + allowed_types = %w[jar pom].freeze + File.open(file, 'w') do |f| + f.puts + f.puts 'The following files have been resolved:' + resolved.each do |dep| + next unless allowed_types.include?(dep.type) + + line = +" #{dep.group_id}:#{dep.artifact_id}:#{dep.type}:" + line << "#{dep.classifier}:" if dep.classifier + line << "#{dep.version}:#{dep.scope}:#{dep.file}" + f.puts line + end + end end end end diff --git a/lib/jars/maven_factory.rb b/lib/jars/maven_factory.rb deleted file mode 100644 index c78fee9..0000000 --- a/lib/jars/maven_factory.rb +++ /dev/null @@ -1,132 +0,0 @@ -# frozen_string_literal: true - -require 'jar_dependencies' -require 'jars/gemspec_artifacts' - -module Jars - class MavenFactory - module AttachJars - def attach_jars(spec, all_dependencies: false) - @index ||= 0 - @done ||= [] - - deps = GemspecArtifacts.new(spec) - deps.artifacts.each do |a| - # for this gemspec we want to include all artifacts but - # for all others we want to exclude provided and test artifacts - next unless !@done.include?(a.key) && (all_dependencies || ((a.scope != 'provided') && (a.scope != 'test'))) - - # ruby dsl is not working reliably for classifier - self["jars.#{@index}"] = a.to_coord_no_classifier - if a.exclusions - jndex = 0 - a.exclusions.each do |ex| - self["jars.#{@index}.exclusions.#{jndex}"] = ex.to_s - jndex += 1 - end - end - self["jars.#{@index}.scope"] = a.scope if a.scope - self["jars.#{@index}.classifier"] = a.classifier if a.classifier - @index += 1 - @done << a.key - end - end - end - - attr_reader :debug, :verbose - - def initialize(options = nil, debug = Jars.debug?, verbose = Jars.verbose?) - @options = (options || {}).dup - @options.delete(:ignore_dependencies) - @debug = debug - @verbose = verbose - @installed_maven = false - end - - def maven_new(pom) - lazy_load_maven - maven = setup(Maven::Ruby::Maven.new) - - maven.extend AttachJars - # TODO: copy pom to tmp dir in case it is not a real file - maven.options['-f'] = pom - maven - end - - private - - def setup(maven) - maven.verbose = @verbose - maven.options['-X'] = nil if @debug - if @verbose - maven.options['-e'] = nil - elsif !@debug - maven.options['--quiet'] = nil - end - maven['verbose'] = (@debug || @verbose) == true - - maven.options['-s'] = Jars::MavenSettings.effective_settings if Jars.maven_settings - - maven['maven.repo.local'] = java.io.File.new(Jars.local_maven_repo).absolute_path.to_s - - maven - end - - def lazy_load_maven - add_gem_to_load_path('ruby-maven') - add_gem_to_load_path('ruby-maven-libs') - if @installed_maven - puts - puts 'using maven for the first time results in maven' - puts 'downloading all its default plugin and can take time.' - puts 'as those plugins get cached on disk and further execution' - puts 'of maven is much faster then the first time.' - puts - end - require 'maven/ruby/maven' - end - - def find_spec_via_rubygems(name, req) - require 'rubygems/dependency' - dep = Gem::Dependency.new(name, req) - dep.matching_specs(true).last - end - - def add_gem_to_load_path(name) - # if the gem is already activated => good - return if Gem.loaded_specs[name] - - # just install gem if needed and add it to the load_path - # and leave activated gems as they are - req = requirement(name) - unless (spec = find_spec_via_rubygems(name, req)) - spec = install_gem(name, req) - end - raise "failed to resolve gem '#{name}' if you're using Bundler add it as a dependency" unless spec - - path = File.join(spec.full_gem_path, spec.require_path) - $LOAD_PATH << path unless $LOAD_PATH.include?(path) - end - - def requirement(name) - jars = Gem.loaded_specs['jar-dependencies'] - dep = jars&.dependencies&.detect { |d| d.name == name } - dep.nil? ? Gem::Requirement.create('>0') : dep.requirement - end - - def install_gem(name, req) - @installed_maven = true - puts "Installing gem '#{name}' . . ." - require 'rubygems/dependency_installer' - inst = Gem::DependencyInstaller.new(@options ||= {}) - inst.install(name, req).first - rescue => e - if Jars.verbose? - warn e.inspect - warn e.backtrace.join("\n") - end - raise "there was an error installing '#{name} (#{req})' " \ - "#{@options[:domain]}. please install it manually: #{e.inspect}" - end - end -end diff --git a/lib/jars/maven_settings.rb b/lib/jars/maven_settings.rb index 7f1b291..39ae004 100644 --- a/lib/jars/maven_settings.rb +++ b/lib/jars/maven_settings.rb @@ -2,8 +2,6 @@ module Jars class MavenSettings - LINE_SEPARATOR = ENV_JAVA['line.separator'] - class << self def local_settings @_jars_maven_local_settings_ = nil unless instance_variable_defined?(:@_jars_maven_local_settings_) @@ -36,36 +34,6 @@ def user_settings @_jars_maven_user_settings_ || nil end - def effective_settings - @_jars_effective_maven_settings_ = nil unless instance_variable_defined?(:@_jars_effective_maven_settings_) - if @_jars_effective_maven_settings_.nil? - begin - require 'rubygems/request' - - http = Gem::Request.proxy_uri(Gem.configuration[:http_proxy] || Gem::Request.get_proxy_from_env('http')) - https = Gem::Request.proxy_uri(Gem.configuration[:https_proxy] || Gem::Request.get_proxy_from_env('https')) - rescue - Jars.debug('ignore rubygems proxy configuration as rubygems is too old') - end - @_jars_effective_maven_settings_ = if http.nil? && https.nil? - settings - else - setup_interpolated_settings(http, https) || settings - end - end - @_jars_effective_maven_settings_ - end - - def cleanup - File.unlink(effective_settings) if effective_settings != settings - ensure - reset - end - - def reset - instance_variables.each { |var| instance_variable_set(var, nil) } - end - def settings @_jars_maven_settings_ = nil unless instance_variable_defined?(:@_jars_maven_settings_) local_settings || user_settings if @_jars_maven_settings_.nil? @@ -85,44 +53,8 @@ def global_settings @_jars_maven_global_settings_ || nil end - private - - def setup_interpolated_settings(http, https) - proxy = raw_proxy_settings_xml(http, https).gsub("\n", LINE_SEPARATOR) - if settings.nil? - raw = "#{LINE_SEPARATOR}#{proxy}" - else - raw = File.read(settings) - if raw.include?('') - Jars.warn("can not interpolated proxy info for #{settings}") - return - else - raw.sub!('', "#{LINE_SEPARATOR}#{proxy}") - end - end - tempfile = java.io.File.create_temp_file('settings', '.xml') - tempfile.delete_on_exit - File.write(tempfile.path, raw) - tempfile.path - end - - def raw_proxy_settings_xml(http, https) - raw = File.read(File.join(File.dirname(__FILE__), 'settings.xml')) - if http - raw.sub!('__HTTP_ACTIVE__', 'true') - raw.sub!('__HTTP_SERVER__', http.host) - raw.sub!('__HTTP_PORT__', http.port.to_s) - else - raw.sub!('__HTTP_ACTIVE__', 'false') - end - if https - raw.sub!('__HTTPS_ACTIVE__', 'true') - raw.sub!('__HTTPS_SERVER__', https.host) - raw.sub!('__HTTPS_PORT__', https.port.to_s) - else - raw.sub!('__HTTPS_ACTIVE__', 'false') - end - raw + def reset + instance_variables.each { |var| instance_variable_set(var, nil) } end end end diff --git a/lib/jars/mima.rb b/lib/jars/mima.rb new file mode 100644 index 0000000..0e26f4f --- /dev/null +++ b/lib/jars/mima.rb @@ -0,0 +1,262 @@ +# frozen_string_literal: true + +require 'jars/mima/version' +require 'jars/gemspec_artifacts' + +module Jars + # Resolver backed by the Mima Java library (MIni MAven). + # Replaces the previous ruby-maven based resolution pipeline. + # + # Mima wraps the Maven Resolver (Aether) API as a standalone library (no Maven process is spawned). + module Mima + class << self + @@jars_loaded = nil # rubocop:disable Style/ClassVars + + # Loads the bundled Mima jars onto the classpath. + # Safe to call multiple times; only loads once. + def ensure_jars_loaded + return if @@jars_loaded + + mima_dir = File.expand_path('mima', File.dirname(__FILE__)) + + load File.join(mima_dir, "slf4j-api-#{SLF4J_VERSION}.jar") + load File.join(mima_dir, "slf4j-simple-#{SLF4J_VERSION}.jar") + load File.join(mima_dir, "jcl-over-slf4j-#{SLF4J_VERSION}.jar") + load File.join(mima_dir, "context-#{MIMA_VERSION}.jar") + load File.join(mima_dir, "standalone-static-uber-#{MIMA_VERSION}.jar") + + @@jars_loaded = true # rubocop:disable Style/ClassVars + end + + # Builds a +ContextOverrides+ from jar-dependencies configuration + # (local repo, user/global settings.xml). + # + # @return [Java::eu.maveniverse.maven.mima.context.ContextOverrides] + def context_overrides + ensure_jars_loaded + + builder = Java::eu.maveniverse.maven.mima.context.ContextOverrides.create + builder.withUserSettings(true) + + # Local repository override + local_repo = ::Jars.local_maven_repo + builder.withLocalRepositoryOverride(java.nio.file.Paths.get(local_repo)) if local_repo + + # User settings.xml override + settings = ::Jars::MavenSettings.settings + builder.withUserSettingsXmlOverride(java.nio.file.Paths.get(settings)) if settings + + # Global settings.xml override + global = ::Jars::MavenSettings.global_settings + builder.withGlobalSettingsXmlOverride(java.nio.file.Paths.get(global)) if global + + builder.build + end + + # Creates a Mima Context. Caller is responsible for closing it. + # + # @param overrides [Java::eu.maveniverse.maven.mima.context.ContextOverrides, nil] + # optional overrides; when +nil+, {#context_overrides} is used + # @return [Java::eu.maveniverse.maven.mima.context.Context] + def create_context(overrides = nil) + ensure_jars_loaded + + overrides ||= context_overrides + runtime = Java::eu.maveniverse.maven.mima.context.Runtimes::INSTANCE.getRuntime + runtime.create(overrides) + end + + # Resolves transitive dependencies for the given artifacts. + # Creates (and closes) its own Mima context. + # + # @param artifacts [Array] jar dependency declarations + # @param all_dependencies [Boolean] when +true+, include provided/test scoped artifacts + # @return [Array] resolved artifacts with local file paths + def resolve_artifacts(artifacts, all_dependencies: false) + context = create_context + begin + resolve_with_context(context, artifacts, all_dependencies: all_dependencies) + ensure + context.close + end + end + + # Resolves transitive dependencies using an existing Mima context. + # + # @param context [Java::eu.maveniverse.maven.mima.context.Context] an open Mima context + # @param artifacts [Array] jar dependency declarations + # @param all_dependencies [Boolean] when +true+, include provided/test scoped artifacts + # @return [Array] resolved artifacts with local file paths + def resolve_with_context(context, artifacts, all_dependencies: false) + deps = artifacts_to_dependencies(artifacts, all_dependencies: all_dependencies) + return [] if deps.empty? + + collect_request = org.eclipse.aether.collection.CollectRequest.new + deps.each { |d| collect_request.addDependency(d) } + collect_request.setRepositories(context.remoteRepositories) + + dependency_request = org.eclipse.aether.resolution.DependencyRequest.new + dependency_request.setCollectRequest(collect_request) + + result = context.repositorySystem.resolveDependencies( + context.repositorySystemSession, dependency_request + ) + + root = result.getRoot + collect_resolved(root) + end + + private + + # Converts {GemspecArtifacts::Artifact} objects to Aether +Dependency+ list, + # filtering by scope unless +all_dependencies+ is set. + # + # @param artifacts [Array] + # @param all_dependencies [Boolean] + # @return [Array] + def artifacts_to_dependencies(artifacts, all_dependencies: false) + filtered = artifacts.select do |a| + all_dependencies || (a.scope != 'provided' && a.scope != 'test') + end + + filtered.map do |a| + aether_artifact = build_aether_artifact(a) + scope = a.scope || 'compile' + dep = org.eclipse.aether.graph.Dependency.new(aether_artifact, scope) + + if a.exclusions && !a.exclusions.empty? + exclusions = a.exclusions.map do |ex| + org.eclipse.aether.graph.Exclusion.new(ex.group_id, ex.artifact_id, '*', '*') + end + dep = dep.setExclusions(exclusions) + end + + dep + end + end + + # Converts a single {GemspecArtifacts::Artifact} to an Aether +DefaultArtifact+. + # + # @param artifact [Jars::GemspecArtifacts::Artifact] + # @return [org.eclipse.aether.artifact.DefaultArtifact] + def build_aether_artifact(artifact) + version = Jars::MavenVersion.new(artifact.version) || artifact.version + if artifact.classifier + org.eclipse.aether.artifact.DefaultArtifact.new( + artifact.group_id, artifact.artifact_id, artifact.classifier, + artifact.type || 'jar', version + ) + else + org.eclipse.aether.artifact.DefaultArtifact.new( + artifact.group_id, artifact.artifact_id, + artifact.type || 'jar', version + ) + end + end + + # Walks the resolved dependency tree and collects {ResolvedDependency} objects. + # + # @param node [org.eclipse.aether.graph.DependencyNode] root of the resolved tree + # @param result [Array] accumulator + # @return [Array] + def collect_resolved(node, result = []) + node.getChildren.each do |child| + dep = child.getDependency + next unless dep + + artifact = dep.getArtifact + next unless artifact.getFile # skip unresolved + + result << ResolvedDependency.new( + artifact.getGroupId, + artifact.getArtifactId, + artifact.getVersion, + artifact.getClassifier.to_s.empty? ? nil : artifact.getClassifier, + artifact.getExtension, + dep.getScope, + artifact.getFile.getAbsolutePath + ) + + collect_resolved(child, result) + end + result + end + end + + # Structured result from dependency resolution. + # + # @!attribute [r] group_id + # @return [String] Maven group ID + # @!attribute [r] artifact_id + # @return [String] Maven artifact ID + # @!attribute [r] version + # @return [String] resolved version + # @!attribute [r] classifier + # @return [String, nil] Maven classifier (e.g. +"sources"+, +"no_aop"+) + # @!attribute [r] type + # @return [String] artifact type/extension (e.g. +"jar"+, +"pom"+) + # @!attribute [r] scope + # @return [String] Maven scope (+"compile"+, +"runtime"+, +"test"+, +"provided"+, +"system"+) + # @!attribute [r] file + # @return [String] absolute path to the resolved artifact in the local repository + ResolvedDependency = Struct.new(:group_id, :artifact_id, :version, :classifier, :type, :scope, :file) do + # @return [Boolean] true if scope is neither +test+ nor +provided+ + def runtime? + scope != 'test' && scope != 'provided' + end + + # @return [Boolean] true if scope is +system+ + def system? + scope == 'system' + end + + # Maven repository layout path relative to repo root. + # + # @return [String] e.g. +"org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar"+ + def path + parts = group_id.split('.') + parts << artifact_id + parts << version + filename = +"#{artifact_id}-#{version}" + filename << "-#{classifier}" if classifier + filename << ".#{type || 'jar'}" + parts << filename + File.join(parts) + end + + # Formats a line suitable for the +Jars.lock+ file. + # + # @return [String] e.g. +"org.slf4j:slf4j-api:1.7.36:compile:"+ + def to_lock_entry + entry = +"#{group_id}:#{artifact_id}:" + entry << "#{classifier}:" if classifier + entry << "#{version}:#{scope}:" + entry + end + + # Colon-separated GAV string suitable for +require_jar+ calls. + # + # @return [String] e.g. +"org.slf4j:slf4j-api:1.7.36"+ + def gav + parts = [group_id, artifact_id] + parts << classifier if classifier + parts << version + parts.join(':') + end + + # Relative jar path for vendoring / +require_jar+. + # + # @return [String] e.g. +"org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar"+ + def jar_path + parts = group_id.split('.') + parts << artifact_id + parts << version + filename = +"#{artifact_id}-#{version}" + filename << "-#{classifier}" if classifier + filename << '.jar' + parts << filename + File.join(parts) + end + end + end +end diff --git a/lib/jars/mima/context-2.4.42.jar b/lib/jars/mima/context-2.4.42.jar new file mode 100644 index 0000000..5acb5bf Binary files /dev/null and b/lib/jars/mima/context-2.4.42.jar differ diff --git a/lib/jars/mima/jcl-over-slf4j-1.7.36.jar b/lib/jars/mima/jcl-over-slf4j-1.7.36.jar new file mode 100644 index 0000000..3ecd7d5 Binary files /dev/null and b/lib/jars/mima/jcl-over-slf4j-1.7.36.jar differ diff --git a/lib/jars/mima/slf4j-api-1.7.36.jar b/lib/jars/mima/slf4j-api-1.7.36.jar new file mode 100644 index 0000000..7d3ce68 Binary files /dev/null and b/lib/jars/mima/slf4j-api-1.7.36.jar differ diff --git a/lib/jars/mima/slf4j-simple-1.7.36.jar b/lib/jars/mima/slf4j-simple-1.7.36.jar new file mode 100644 index 0000000..ef831a8 Binary files /dev/null and b/lib/jars/mima/slf4j-simple-1.7.36.jar differ diff --git a/lib/jars/mima/standalone-static-uber-2.4.42.jar b/lib/jars/mima/standalone-static-uber-2.4.42.jar new file mode 100644 index 0000000..c824521 Binary files /dev/null and b/lib/jars/mima/standalone-static-uber-2.4.42.jar differ diff --git a/lib/jars/mima/version.rb b/lib/jars/mima/version.rb new file mode 100644 index 0000000..dc0f535 --- /dev/null +++ b/lib/jars/mima/version.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module Jars + module Mima + MIMA_VERSION = '2.4.42' + SLF4J_VERSION = '1.7.36' + end +end diff --git a/lib/jars/output_jars_pom.rb b/lib/jars/output_jars_pom.rb deleted file mode 100644 index f75cf9f..0000000 --- a/lib/jars/output_jars_pom.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -# this file is maven DSL - -if ENV_JAVA['jars.quiet'] != 'true' - model.dependencies.each do |d| - puts " #{d.group_id}:#{d.artifact_id}" \ - "#{":#{d.classifier}" if d.classifier}" \ - ":#{d.version}:#{d.scope || 'compile'}" - next if d.exclusions.empty? - - puts " exclusions: #{d.exclusions.collect do |e| - "#{e.group_id}:#{e.artifact_id}" - end.join}" - end -end diff --git a/lib/jars/settings.xml b/lib/jars/settings.xml deleted file mode 100644 index 676e4ca..0000000 --- a/lib/jars/settings.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - __HTTP_ACTIVE__ - http - __HTTP_SERVER__ - __HTTP_PORT__ - - - - __HTTPS_ACTIVE__ - https - __HTTPS_SERVER__ - __HTTPS_PORT__ - - - diff --git a/lib/jars/version.rb b/lib/jars/version.rb index d6c8807..585a066 100644 --- a/lib/jars/version.rb +++ b/lib/jars/version.rb @@ -2,6 +2,4 @@ module Jars VERSION = '0.5.7' - JRUBY_PLUGINS_VERSION = '3.0.2' - DEPENDENCY_PLUGIN_VERSION = '2.8' end diff --git a/specs/maven_factory_spec.rb b/specs/maven_factory_spec.rb deleted file mode 100644 index a248b08..0000000 --- a/specs/maven_factory_spec.rb +++ /dev/null @@ -1,77 +0,0 @@ -# frozen_string_literal: true - -require File.expand_path('setup', File.dirname(__FILE__)) - -require 'jars/maven_factory' - -describe Jars::MavenFactory do - after do - ENV['JARS_VERBOSE'] = nil - ENV['JARS_DEBUG'] = nil - ENV['JARS_MAVEN_SETTINGS'] = nil - Jars.reset - end - - it 'uses logging config' do - ENV['JARS_VERBOSE'] = nil - ENV['JARS_DEBUG'] = nil - Jars.reset - maven = Jars::MavenFactory.new.maven_new('pom') - _(maven.options.key?('--quiet')).must_equal true - _(maven.options.key?('-X')).must_equal false - _(maven.options['-Dverbose']).must_equal false - - ENV['JARS_VERBOSE'] = 'true' - ENV['JARS_DEBUG'] = nil - Jars.reset - maven = Jars::MavenFactory.new.maven_new('pom') - _(maven.options.key?('--quiet')).must_equal false - _(maven.options.key?('-e')).must_equal true - _(maven.options.key?('-X')).must_equal false - _(maven.options['-Dverbose']).must_equal true - - ENV['JARS_VERBOSE'] = nil - ENV['JARS_DEBUG'] = 'true' - Jars.reset - maven = Jars::MavenFactory.new.maven_new('pom') - _(maven.options.key?('--quiet')).must_equal false - _(maven.options.key?('-e')).must_equal false - _(maven.options.key?('-X')).must_equal true - _(maven.options['-Dverbose']).must_equal true - - ENV['JARS_VERBOSE'] = 'true' - ENV['JARS_DEBUG'] = 'true' - Jars.reset - maven = Jars::MavenFactory.new.maven_new('pom') - _(maven.options.key?('--quiet')).must_equal false - _(maven.options.key?('-e')).must_equal true - _(maven.options.key?('-X')).must_equal true - _(maven.options['-Dverbose']).must_equal true - end - - it 'uses proxy settings from Gem.configuration' do - skip('pending until it realy works') - ENV['JARS_MAVEN_SETTINGS'] = 'specs/does/no/exists/settings.xml' - Gem.configuration[:proxy] = 'https://localhost:3128' - Jars.reset - maven = Jars::MavenFactory.new.maven_new('pom') - _(maven.options.key?('-DproxySet=true')).must_equal true - _(maven.options.key?('-DproxyHost=localhost')).must_equal true - _(maven.options.key?('-DproxyPort=3128')).must_equal true - - Gem.configuration[:proxy] = :noproxy - Jars.reset - maven = Jars::MavenFactory.new.maven_new('pom') - _(maven.options.key?('-DproxySet=true')).must_equal false - _(maven.options.key?('-DproxyHost=localhost')).must_equal false - _(maven.options.key?('-DproxyPort=3128')).must_equal false - - ENV['JARS_MAVEN_SETTINGS'] = 'specs/settings.xml' - Gem.configuration[:proxy] = 'https://localhost:3128' - Jars.reset - maven = Jars::MavenFactory.new.maven_new('pom') - _(maven.options.key?('-DproxySet=true')).must_equal false - _(maven.options.key?('-DproxyHost=localhost')).must_equal false - _(maven.options.key?('-DproxyPort=3128')).must_equal false - end -end