diff --git a/spec/integration/list_spec.cr b/spec/integration/list_spec.cr index ec2aa566..37ec4a1b 100644 --- a/spec/integration/list_spec.cr +++ b/spec/integration/list_spec.cr @@ -49,4 +49,15 @@ describe "list" do stdout.should contain(" * shoulda (0.1.0)") end end + + it "show error when dependencies are not installed" do + metadata = { + dependencies: {web: "*", orm: "*"}, + development_dependencies: {mock: "*"}, + } + with_shard(metadata) do + ex = expect_raises(FailedCommand) { run "shards list --no-color" } + ex.stdout.should contain("Dependencies aren't satisfied. Install them with 'shards install'") + end + end end diff --git a/spec/unit/git_resolver_spec.cr b/spec/unit/git_resolver_spec.cr index 63f574dc..8984ee63 100644 --- a/spec/unit/git_resolver_spec.cr +++ b/spec/unit/git_resolver_spec.cr @@ -77,25 +77,25 @@ module Shards it "install" do library = resolver("library") - library.install(version "0.1.2") + library.install_sources(version("0.1.2"), install_path("library")) File.exists?(install_path("library", "src/library.cr")).should be_true File.exists?(install_path("library", "shard.yml")).should be_true - library.installed_spec.not_nil!.version.should eq(version "0.1.2") + Spec.from_file(install_path("library", "shard.yml")).version.should eq(version "0.1.2") - library.install(version "0.2.0") - library.installed_spec.not_nil!.version.should eq(version "0.2.0") + library.install_sources(version("0.2.0"), install_path("library")) + Spec.from_file(install_path("library", "shard.yml")).version.should eq(version "0.2.0") end it "install commit" do library = resolver("library") version = version "0.2.0+git.commit.#{git_commits(:library)[0]}" - library.install(version) - library.installed_spec.not_nil!.version.should eq(version) + library.install_sources(version, install_path("library")) + Spec.from_file(install_path("library", "shard.yml")).version.should eq(version "0.2.0") end it "origin changed" do library = GitResolver.new("library", git_url("library")) - library.install(version "0.1.2") + library.install_sources(version("0.1.2"), install_path("library")) # Change the origin in the cache repo to https://github.com/foo/bar Dir.cd(library.local_path) do diff --git a/spec/unit/package_spec.cr b/spec/unit/package_spec.cr new file mode 100644 index 00000000..4a1e32cf --- /dev/null +++ b/spec/unit/package_spec.cr @@ -0,0 +1,85 @@ +require "./spec_helper" +require "../../src/package" + +private def resolver(name) + Shards::PathResolver.new(name, git_path(name)) +end + +private def git_resolver(name) + Shards::GitResolver.new(name, git_path(name)) +end + +module Shards + describe Package do + before_each do + create_path_repository "library", "1.2.3" + create_git_repository "repo", "0.1.2", "0.1.3" + end + + it "installs" do + package = Package.new("library", resolver("library"), version "1.2.3") + package.installed?.should be_false + package.install + package.installed?.should be_true + end + + it "reads spec from installed dir" do + package = Package.new("repo", git_resolver("repo"), version "0.1.2") + package.install + + File.open(install_path("repo", "shard.yml"), "a") do |f| + f.puts "license: FOO" + end + + package.spec.license.should eq("FOO") + end + + it "fallback to resolver to read spec" do + package = Package.new("repo", git_resolver("repo"), version "0.1.2") + package.install + File.delete install_path("repo", "shard.yml") + package.spec.version.should eq(version "0.1.2") + end + + it "reads spec from resolver if not installed" do + package = Package.new("repo", git_resolver("repo"), version "0.1.3") + package.install + + package = Package.new("repo", git_resolver("repo"), version "0.1.2") + package.spec.original_version.should eq(version "0.1.2") + end + + it "different version is not installed" do + package = Package.new("library", resolver("library"), version "1.2.3") + package.install + + package2 = Package.new("library", resolver("library"), version "2.0.0") + package2.installed?.should be_false + end + + it "different resolver is not installed" do + package = Package.new("library", resolver("library"), version "1.2.3") + package.install + + package2 = Package.new("library", resolver("foo"), version "1.2.3") + package2.installed?.should be_false + end + + it "not installed if missing target" do + package = Package.new("library", resolver("library"), version "1.2.3") + package.install + + run "rm -rf #{install_path("library")}" + package.installed?.should be_false + end + + it "cleanups target before installing" do + Dir.mkdir_p(install_path) + File.touch(install_path("library")) + package = Package.new("library", resolver("library"), version "1.2.3") + package.install + + File.symlink?(install_path("library")).should be_true + end + end +end diff --git a/spec/unit/path_resolver_spec.cr b/spec/unit/path_resolver_spec.cr index 528e1c0a..1b868f1b 100644 --- a/spec/unit/path_resolver_spec.cr +++ b/spec/unit/path_resolver_spec.cr @@ -20,61 +20,16 @@ module Shards it "install" do resolver("library").tap do |library| - library.install(version "1.2.3") + library.install_sources(version("1.2.3"), install_path("library")) File.exists?(install_path("library", "src/library.cr")).should be_true File.exists?(install_path("library", "shard.yml")).should be_true - library.installed_spec.not_nil!.version.should eq(version "1.2.3") + Spec.from_file(install_path("library", "shard.yml")).version.should eq(version "1.2.3") end end it "install fails when path doesnt exist" do - expect_raises(Error) { resolver("unknown").install(version "1.0.0") } - end - - it "installed reports library is installed" do - resolver("library").tap do |resolver| - resolver.installed?.should be_false - - resolver.install(version "1.2.3") - resolver.installed?.should be_true - end - end - - it "installed when target is incorrect link" do - resolver("library").tap do |resolver| - resolver.install(version "1.2.3") - resolver.installed?.should be_true - end - end - - it "installed when target is incorrect broken link" do - resolver("library").tap do |resolver| - File.symlink("/does-not-exist", resolver.install_path) - resolver.installed?.should be_false - - resolver.install(version "1.2.3") - resolver.installed?.should be_true - end - end - - it "installed when target is dir" do - resolver("library").tap do |resolver| - Dir.mkdir_p(resolver.install_path) - File.touch(File.join(resolver.install_path, "foo")) - resolver.installed?.should be_false - - resolver.install(version "1.2.3") - resolver.installed?.should be_true - end - end - - it "installed when target is file" do - resolver("library").tap do |resolver| - File.touch(resolver.install_path) - resolver.installed?.should be_false - - resolver.install(version "1.2.3") - resolver.installed?.should be_true + expect_raises(Error) do + resolver("unknown").install_sources(version("1.0.0"), install_path("unknown")) end end diff --git a/spec/unit/resolver_spec.cr b/spec/unit/resolver_spec.cr index d5f14f59..bc56bc78 100644 --- a/spec/unit/resolver_spec.cr +++ b/spec/unit/resolver_spec.cr @@ -17,21 +17,6 @@ module Shards resolver.should_not eq(GitResolver.new("name", "/path")) end - describe "#installed_spec" do - it "reports parse error location" do - create_path_repository "foo", "1.2.3" - create_file "foo", "shard.yml", "name: foo\nname: foo\n" - - resolver = Shards::PathResolver.new("foo", git_path("foo")) - resolver.install Shards::Version.new("1.2.3") - - error = expect_raises(ParseError, %(Error in foo:spec/unit/.lib/foo/shard.yml: duplicate attribute "name" at line 2, column 1)) do - resolver.installed_spec - end - error.resolver.should eq resolver - end - end - describe "#spec" do it "reports parse error location" do create_path_repository "foo", "1.2.3" diff --git a/spec/unit/spec_helper.cr b/spec/unit/spec_helper.cr index 1adc1dac..3c2a1f3d 100644 --- a/spec/unit/spec_helper.cr +++ b/spec/unit/spec_helper.cr @@ -16,12 +16,13 @@ end Spec.before_each do clear_repositories Shards::Resolver.clear_resolver_cache + Shards.info.reload end private def clear_repositories run "rm -rf #{tmp_path}/*" - run "rm -rf #{Shards.cache_path}/*" - run "rm -rf #{Shards.install_path}/*" + run "rm -rf #{Shards.cache_path}" + run "rm -rf #{Shards.install_path}" end def install_path(project, *path_names) diff --git a/src/commands/check.cr b/src/commands/check.cr index 936ba1e1..9fc27c37 100644 --- a/src/commands/check.cr +++ b/src/commands/check.cr @@ -22,21 +22,14 @@ module Shards dependencies.each do |dependency| Log.debug { "#{dependency.name}: checking..." } - unless _spec = dependency.resolver.installed_spec - Log.debug { "#{dependency.name}: not installed" } + unless installed?(dependency) raise Error.new("Dependencies aren't satisfied. Install them with 'shards install'") end - - unless installed?(dependency, _spec) - raise Error.new("Dependencies aren't satisfied. Install them with 'shards install'") - end - - verify(_spec.dependencies) end end - private def installed?(dependency, spec) - unless lock = locks.shards.find { |d| d.name == spec.name } + private def installed?(dependency) + unless lock = locks.shards.find { |d| d.name == dependency.name } Log.debug { "#{dependency.name}: not locked" } return false end @@ -46,13 +39,14 @@ module Shards Log.debug { "#{dependency.name}: lock conflict" } return false else - return spec.version == version + package = Package.new(lock.name, lock.resolver, version) + return false unless package.installed? + verify(package.spec.dependencies) + return true end else raise Error.new("Invalid #{LOCK_FILENAME}. Please run `shards install` to fix it.") end - - true end end end diff --git a/src/commands/list.cr b/src/commands/list.cr index 0ef4ab53..8e5b1fbe 100644 --- a/src/commands/list.cr +++ b/src/commands/list.cr @@ -14,19 +14,21 @@ module Shards private def list(dependencies, level = 1) dependencies.each do |dependency| - resolver = dependency.resolver - - # FIXME: duplicated from Check#verify - unless _spec = resolver.installed_spec + installed = Shards.info.installed[dependency.name]? + unless installed Log.debug { "#{dependency.name}: not installed" } raise Error.new("Dependencies aren't satisfied. Install them with 'shards install'") end + version = installed.requirement.as(Shards::Version) + package = Package.new(installed.name, installed.resolver, version) + resolver = installed.resolver + indent = " " * level - puts "#{indent}* #{_spec.name} (#{resolver.report_version _spec.version})" + puts "#{indent}* #{dependency.name} (#{resolver.report_version version})" indent_level = @tree ? level + 1 : level - list(_spec.dependencies, indent_level) + list(package.spec.dependencies, indent_level) end end diff --git a/src/commands/outdated.cr b/src/commands/outdated.cr index 60491d01..75e07421 100644 --- a/src/commands/outdated.cr +++ b/src/commands/outdated.cr @@ -29,14 +29,14 @@ module Shards end private def analyze(package) - resolver = package.resolver - installed = resolver.installed_spec.try(&.version) - - unless installed + unless installed_dep = Shards.info.installed[package.name]? Log.warn { "#{package.name}: not installed" } return end + resolver = package.resolver + installed = installed_dep.requirement.as(Shards::Version) + # already the latest version? available_versions = if @prereleases diff --git a/src/info.cr b/src/info.cr index 8178766c..5b852c3e 100644 --- a/src/info.cr +++ b/src/info.cr @@ -12,6 +12,8 @@ class Shards::Info path = info_path if File.exists?(path) @installed = Lock.from_file(path).shards.index_by &.name + else + @installed.clear end end diff --git a/src/package.cr b/src/package.cr index 973f74cf..d723ac90 100644 --- a/src/package.cr +++ b/src/package.cr @@ -8,7 +8,7 @@ module Shards getter is_override : Bool @spec : Spec? - def initialize(@name, @resolver, @version, @is_override) + def initialize(@name, @resolver, @version, @is_override = false) end def report_version @@ -16,10 +16,33 @@ module Shards end def spec - @spec ||= resolver.spec(version) + @spec ||= begin + if installed? + read_installed_spec + else + resolver.spec(version) + end + end + end + + private def read_installed_spec + path = File.join(install_path, SPEC_FILENAME) + unless File.exists?(path) + return resolver.spec(version) + end + + begin + spec = Spec.from_file(path) + spec.version = version + spec + rescue error : ParseError + error.resolver = resolver + raise error + end end def installed? + return false unless File.exists?(install_path) if installed = Shards.info.installed[name]? installed.resolver == resolver && installed.requirement == version else @@ -27,28 +50,49 @@ module Shards end end + def install_path + File.join(Shards.install_path, name) + end + def install + cleanup_install_directory + # install the shard: - resolver.install(version) + resolver.install_sources(version, install_path) # link the project's lib path as the shard's lib path, so the dependency # can access transitive dependencies: unless resolver.is_a?(PathResolver) - lib_path = File.join(resolver.install_path, Shards::INSTALL_DIR) + lib_path = File.join(install_path, Shards::INSTALL_DIR) Log.debug { "Link #{Shards.install_path} to #{lib_path}" } Dir.mkdir_p(File.dirname(lib_path)) target = File.join(Path.new(Shards::INSTALL_DIR).parts.map { ".." }) File.symlink(target, lib_path) end + + Shards.info.installed[name] = Dependency.new(name, resolver, version) + Shards.info.save + end + + protected def cleanup_install_directory + Log.debug { "rm -rf '#{Helpers::Path.escape(install_path)}'" } + FileUtils.rm_rf(install_path) end def postinstall - resolver.run_script("postinstall") + run_script("postinstall") rescue ex : Script::Error - resolver.cleanup_install_directory + cleanup_install_directory raise ex end + def run_script(name) + if installed? && (command = spec.scripts[name]?) + Log.info { "#{name.capitalize} of #{self.name}: #{command}" } + Script.run(install_path, command, name, self.name) + end + end + def install_executables return if !installed? || spec.executables.empty? @@ -56,7 +100,7 @@ module Shards spec.executables.each do |name| Log.debug { "Install bin/#{name}" } - source = File.join(resolver.install_path, "bin", name) + source = File.join(install_path, "bin", name) destination = File.join(Shards.bin_path, name) if File.exists?(destination) diff --git a/src/resolvers/crystal.cr b/src/resolvers/crystal.cr index d99fee7f..a7570a1d 100644 --- a/src/resolvers/crystal.cr +++ b/src/resolvers/crystal.cr @@ -14,7 +14,7 @@ module Shards nil end - def install_sources(version : Version) + def install_sources(version : Version, install_path : String) raise NotImplementedError.new("CrystalResolver#install_sources") end diff --git a/src/resolvers/git.cr b/src/resolvers/git.cr index dad91c9d..7d9145f6 100644 --- a/src/resolvers/git.cr +++ b/src/resolvers/git.cr @@ -209,7 +209,7 @@ module Shards end end - def install_sources(version : Version) + def install_sources(version : Version, install_path : String) update_local_cache ref = git_ref(version) diff --git a/src/resolvers/path.cr b/src/resolvers/path.cr index 17f88562..5f7ec54b 100644 --- a/src/resolvers/path.cr +++ b/src/resolvers/path.cr @@ -20,19 +20,6 @@ module Shards load_spec(version) || raise Error.new("Can't read spec for #{name.inspect}") end - def installed? - File.symlink?(install_path) && check_install_path_target - end - - private def check_install_path_target - begin - real_install_path = File.real_path(install_path) - rescue File::NotFoundError - return false - end - real_install_path == expanded_local_path - end - def available_releases : Array(Version) [spec(nil).version] end @@ -47,7 +34,7 @@ module Shards end end - def install_sources(version) + def install_sources(version, install_path) path = expanded_local_path Dir.mkdir_p(File.dirname(install_path)) diff --git a/src/resolvers/resolver.cr b/src/resolvers/resolver.cr index 1e6964db..6f07bbee 100644 --- a/src/resolvers/resolver.cr +++ b/src/resolvers/resolver.cr @@ -31,36 +31,6 @@ module Shards "#{self.class.key}: #{source}" end - def installed_spec - return unless installed? - - path = File.join(install_path, SPEC_FILENAME) - if installed = Shards.info.installed[name]? - version = installed.requirement.as?(Version) - end - - unless File.exists?(path) - if version - return Spec.new(name, version) - else - raise Error.new("Missing #{SPEC_FILENAME.inspect} for #{name.inspect}") - end - end - - begin - spec = Spec.from_file(path) - spec.version = version if version - spec - rescue error : ParseError - error.resolver = self - raise error - end - end - - def installed? - File.exists?(install_path) && Shards.info.installed.has_key?(name) - end - def versions_for(req : Requirement) : Array(Version) case req when Version then [req] @@ -111,33 +81,9 @@ module Shards end abstract def read_spec(version : Version) : String? - abstract def install_sources(version : Version) + abstract def install_sources(version : Version, install_dir : String) abstract def report_version(version : Version) : String - def install(version : Version) - cleanup_install_directory - - install_sources(version) - Shards.info.installed[name] = Dependency.new(name, self, version) - Shards.info.save - end - - def run_script(name) - if installed? && (command = installed_spec.try(&.scripts[name]?)) - Log.info { "#{name.capitalize} of #{self.name}: #{command}" } - Script.run(install_path, command, name, self.name) - end - end - - def install_path - File.join(Shards.install_path, name) - end - - protected def cleanup_install_directory - Log.debug { "rm -rf '#{Helpers::Path.escape(install_path)}'" } - FileUtils.rm_rf(install_path) - end - def parse_requirement(params : Hash(String, String)) : Requirement if version = params["version"]? VersionReq.new version @@ -146,8 +92,6 @@ module Shards end end - # abstract def write_requirement(req : Requirement, yaml : YAML::Builder) - private record ResolverCacheKey, key : String, name : String, source : String private RESOLVER_CLASSES = {} of String => Resolver.class private RESOLVER_CACHE = {} of ResolverCacheKey => Resolver