Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vendor lib/molinillo #663

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/.crystal
/.shards
/lib
/bin/shards
/bin/shards.dwarf
/bin/shards.exe
Expand Down
8 changes: 2 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,6 @@ BINDIR ?= $(DESTDIR)$(PREFIX)/bin
MANDIR ?= $(DESTDIR)$(PREFIX)/share/man
INSTALL ?= /usr/bin/install

MOLINILLO_VERSION = $(shell $(CRYSTAL) eval 'require "yaml"; puts YAML.parse(File.read("shard.lock"))["shards"]["molinillo"]["version"]')
MOLINILLO_URL = "https://github.com/crystal-lang/crystal-molinillo/archive/v$(MOLINILLO_VERSION).tar.gz"

# MSYS2 support (native Windows should use `Makefile.win` instead)
ifeq ($(OS),Windows_NT)
EXE := .exe
Expand Down Expand Up @@ -105,11 +102,10 @@ test_integration: bin/shards$(EXE)
$(CRYSTAL) spec ./spec/integration/

lib: shard.lock
mkdir -p lib/molinillo
$(SHARDS) install || (curl -L $(MOLINILLO_URL) | tar -xzf - -C lib/molinillo --strip-components=1)
$(SHARDS) install

shard.lock: shard.yml
[ $(SHARDS) = false ] || $(SHARDS) update
$(SHARDS) update

man/%.gz: man/%
gzip -c -9 $< > $@
Expand Down
6 changes: 6 additions & 0 deletions lib/.shards.info
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
version: 1.0
shards:
molinillo:
git: https://github.com/crystal-lang/crystal-molinillo.git
version: 0.2.0
9 changes: 9 additions & 0 deletions lib/molinillo/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
root = true

[*.cr]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
18 changes: 18 additions & 0 deletions lib/molinillo/.github/workflows/crystal.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: Crystal CI

on: [push]

jobs:
build:

runs-on: ubuntu-latest

container:
image: crystallang/crystal

steps:
- uses: actions/checkout@v2
- name: Install dependencies
run: shards install
- name: Run tests
run: crystal spec
9 changes: 9 additions & 0 deletions lib/molinillo/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/docs/
/lib/
/bin/
/.shards/
*.dwarf

# Libraries don't need dependency lock
# Dependencies will be locked in applications that use them
/shard.lock
3 changes: 3 additions & 0 deletions lib/molinillo/.gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "spec/fixture"]
path = spec/fixture
url = https://github.com/CocoaPods/Resolver-Integration-Specs
6 changes: 6 additions & 0 deletions lib/molinillo/.travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
language: crystal

# Uncomment the following if you'd like Travis to run specs and check code formatting
# script:
# - crystal spec
# - crystal tool format --check
22 changes: 22 additions & 0 deletions lib/molinillo/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
This project is licensed under the MIT license.

Copyright (c) 2020 Manas Technology Solutions
Copyright (c) 2014 Samuel E. Giddins [email protected]

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
43 changes: 43 additions & 0 deletions lib/molinillo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# crystal-molinillo

A port of [Molinillo](https://github.com/CocoaPods/Molinillo/) (generic dependency resolution algorithm) to [Crystal](https://crystal-lang.org)

## Installation

1. Add the dependency to your `shard.yml`:

```yaml
dependencies:
molinillo:
github: crystal-lang/crystal-molinillo
```

2. Run `shards install`

## Usage

```crystal
require "molinillo"
```

This was built to be used by [Shards](https://github.com/crystal-lang/shards). Check [`MolinilloSolver`](https://github.com/crystal-lang/shards/blob/master/src/molinillo_solver.cr) for an example of integration.

## Development

This code uses a subrepository with test fixtures. Make sure you clone the repository with `--recursive` before running tests:

```
git clone --recursive https://github.com/crystal-lang/crystal-molinillo
```

## Contributing

1. Fork it (<https://github.com/crystal-lang/crystal-molinillo/fork>)
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create a new Pull Request

## Contributors

- [Juan Wajnerman](https://github.com/waj) - creator and maintainer
1 change: 1 addition & 0 deletions lib/molinillo/lib
9 changes: 9 additions & 0 deletions lib/molinillo/shard.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
name: molinillo
version: 0.2.0

authors:
- Juan Wajnerman <[email protected]>

crystal: ">= 0.35.0, < 2.0.0"

license: MIT
38 changes: 38 additions & 0 deletions lib/molinillo/spec/dependency_graph/log_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# frozen_string_literal: true

require "../spec_helper"

alias DG = Molinillo::DependencyGraph(Int32, Int32)

def shared_examples_for_replay(prepare)
it "replays the log" do
copy = DG.new
graph = DG.new.tap { |g| prepare.call(g) }
graph.log.each &.up(copy)
copy.should eq(graph)
end

it "can undo to an empty graph" do
graph = DG.new
tag = Reference.new
graph.tag(tag)
prepare.call(graph)
graph.rewind_to(tag)
graph.should eq(DG.new)
end
end

describe Molinillo::DependencyGraph::Log do
describe "with empty log" do
shared_examples_for_replay ->(g : DG) {}
end

describe "with some graph" do
shared_examples_for_replay ->(g : DG) do
g.add_child_vertex("Foo", 1, [nil] of String?, 4)
g.add_child_vertex("Bar", 2, ["Foo", nil], 3)
g.add_child_vertex("Baz", 3, %w(Foo Bar), 2)
g.add_child_vertex("Foo", 4, [] of String?, 1)
end
end
end
76 changes: 76 additions & 0 deletions lib/molinillo/spec/dependency_graph_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
require "./spec_helper"

private def test_dependency_graph
graph = Molinillo::DependencyGraph(String, String).new
root = graph.add_vertex("Root", "Root", true)
root2 = graph.add_vertex("Root2", "Root2", true)
child = graph.add_child_vertex("Child", "Child", %w(Root), "Child")
{graph: graph, root: root, root2: root2, child: child}
end

describe Molinillo::DependencyGraph do
describe "in general" do
it "returns root vertices by name" do
data = test_dependency_graph
data[:graph].root_vertex_named("Root").should eq(data[:root])
end

it "returns vertices by name" do
data = test_dependency_graph
data[:graph].vertex_named("Root").should eq(data[:root])
data[:graph].vertex_named("Child").should eq(data[:child])
end

it "returns nil for non-existent root vertices" do
data = test_dependency_graph
data[:graph].root_vertex_named("missing").should be_nil
end

it "returns nil for non-existent vertices" do
data = test_dependency_graph
data[:graph].vertex_named("missing").should be_nil
end
end

describe "detaching a vertex" do
it "detaches a root vertex without successors" do
graph = Molinillo::DependencyGraph(String, String).new
root = graph.add_vertex("root", "root", true)
graph.detach_vertex_named(root.name)
graph.vertex_named(root.name).should be_nil
graph.vertices.should be_empty
end

it "detaches a root vertex with successors" do
graph = Molinillo::DependencyGraph(String, String).new
root = graph.add_vertex("root", "root", true)
child = graph.add_child_vertex("child", "child", %w(root), "child")
graph.detach_vertex_named(root.name)
graph.vertex_named(root.name).should be_nil
graph.vertex_named(child.name).should be_nil
graph.vertices.should be_empty
end

it "detaches a root vertex with successors with other parents" do
graph = Molinillo::DependencyGraph(String, String).new
root = graph.add_vertex("root", "root", true)
root2 = graph.add_vertex("root2", "root2", true)
child = graph.add_child_vertex("child", "child", %w(root root2), "child")
graph.detach_vertex_named(root.name)
graph.vertex_named(root.name).should be_nil
graph.vertex_named(child.name).should eq(child)
child.predecessors.should eq([root2])
graph.vertices.size.should eq(2)
end

it "detaches a vertex with predecessors" do
graph = Molinillo::DependencyGraph(String, String).new
parent = graph.add_vertex("parent", "parent", true)
child = graph.add_child_vertex("child", "child", %w(parent), "child")
graph.detach_vertex_named(child.name)
graph.vertex_named(child.name).should be_nil
graph.vertices.should eq({parent.name => parent})
parent.outgoing_edges.should be_empty
end
end
end
115 changes: 115 additions & 0 deletions lib/molinillo/spec/resolver_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
require "./spec_helper"

module Molinillo
FIXTURE_CASE_DIR = FIXTURE_DIR / "case"

class TestCase
getter fixture : Fixture
getter name : String
@index : SpecificationProvider(Gem::Dependency | TestSpecification, TestSpecification)?
@requested : Array(Gem::Dependency | TestSpecification)?
@result : DependencyGraph(TestSpecification?, TestSpecification?)?
@conflicts : Set(String)?
@@all : Array(TestCase)?

def self.from_fixture(fixture_path)
fixture = File.open(fixture_path) { |f| Fixture.from_json(f) }
new(fixture)
end

def initialize(@fixture)
@name = fixture.name
end

def index
@index ||= TestIndex.from_fixture(@fixture.index || "awesome")
end

def requested
@requested ||= @fixture.requested.map do |(name, reqs)|
Gem::Dependency.new(name.delete("\x01"), reqs.split(',').map(&.chomp)).as(Gem::Dependency | TestSpecification)
end
end

def add_dependencies_to_graph(graph : DependencyGraph(P, P), parent, hash, all_parents = Set(DependencyGraph::Vertex(P, P)).new) forall P
name = hash.name
version = hash.version # Gem::Version.new(hash['version'])
dependency = index.specs[name].find { |s| Shards::Versions.compare(s.version, version) == 0 }.not_nil!
vertex = if parent
graph.add_vertex(name, dependency).tap do |v|
graph.add_edge(parent, v, dependency)
end
else
graph.add_vertex(name, dependency, true)
end
return unless all_parents.add?(vertex)
hash.dependencies.each do |dep|
add_dependencies_to_graph(graph, vertex, dep, all_parents)
end
end

def result
@result ||= @fixture.resolved.reduce(DependencyGraph(TestSpecification?, TestSpecification?).new) do |graph, r|
graph.tap do |g|
add_dependencies_to_graph(g, nil, r)
end
end
end

def base
@fixture.base.reduce(DependencyGraph(Gem::Dependency | TestSpecification, Gem::Dependency | TestSpecification).new) do |graph, r|
graph.tap do |g|
add_dependencies_to_graph(g, nil, r)
end
end
end

def conflicts
@conflicts ||= @fixture.conflicts.to_set
end

def self.all
@@all ||= Dir.glob(FIXTURE_CASE_DIR.to_s + "**/*.json").map { |fixture| TestCase.from_fixture(fixture) }
end

def resolve(index_class)
index = index_class.new(self.index.specs)
resolver = Resolver(Gem::Dependency | TestSpecification, TestSpecification).new(index, TestUI.new)
resolver.resolve(requested, base)
end

def run(index_class)
it name do
# skip 'does not yet reliably pass' if test_case.ignore?(index_class)
if fixture.conflicts.any?
error = expect_raises(ResolverError) { resolve(index_class) }
names = case error
when CircularDependencyError
error.vertices.map &.name
when VersionConflict
error.conflicts.keys
else
fail "Unexpected error type: #{error}"
end.to_set
names.should eq(self.conflicts)
else
result = resolve(index_class)

result.should eq(self.result)
end
end
end
end

describe Resolver do
describe "dependency resolution" do
describe "with the TestIndex index" do
TestCase.all.each &.run(TestIndex)
end
end
end
end

# it "list all cases" do
# pp Molinillo::TestCase.all
# end
Loading