Skip to content

Commit 172f555

Browse files
committed
refactor(*): rework to include shards, wrap install for geode
1 parent d0bce33 commit 172f555

File tree

6 files changed

+170
-81
lines changed

6 files changed

+170
-81
lines changed

shard.lock

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@ shards:
1212
git: https://github.com/devnote-dev/license.git
1313
version: 0.1.0+git.commit.f95e1c1c95d1ef9d50097b38f4e6bc688629601a
1414

15+
molinillo:
16+
git: https://github.com/crystal-lang/crystal-molinillo.git
17+
version: 0.2.0
18+
19+
shards:
20+
git: https://github.com/crystal-lang/shards.git
21+
version: 0.18.0
22+
1523
trigram:
1624
git: https://github.com/devnote-dev/trigram.git
1725
version: 0.1.0+git.commit.57df0066d2508d9b472be40babd3571b7b5bd772

shard.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ dependencies:
1717
github: devnote-dev/license
1818
branch: main
1919

20+
shards:
21+
github: crystal-lang/shards
22+
2023
trigram:
2124
github: devnote-dev/trigram
2225

src/commands/install.cr

Lines changed: 19 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ module Geode::Commands
55
@summary = "install dependencies from shard.yml"
66
@description = <<-DESC
77
Installs dependencies from a shard.yml file. This includes development dependencies
8-
unless you include the '--production' flag.
8+
unless you specify the '--production' flag.
99
DESC
1010

1111
add_usage "install [-D|--without-development] [-E|--skip-executables] [--frozen]" \
@@ -47,95 +47,33 @@ module Geode::Commands
4747
def run(arguments : Cling::Arguments, options : Cling::Options) : Nil
4848
ensure_local_shard!
4949

50-
shards = Process.find_executable "shards"
51-
fatal(
52-
"Could not find the Shards executable",
53-
"(wrapped around for dependency resolution)",
54-
) unless shards
55-
56-
args = %w[install --skip-executables --skip-postinstall]
57-
args << "--frozen" if options.has? "frozen"
58-
args << "--no-color" if options.has? "no-color"
59-
if value = options.get?("jobs").try &.as_i
60-
args << "--jobs=#{value}"
61-
end
62-
args << "--local" if options.has? "local"
63-
args << "--production" if options.has? "production"
64-
args << "--without-development" if options.has? "without-development"
65-
66-
start = Time.monotonic
67-
deps = [] of String
68-
69-
Process.run(shards, args) do |proc|
70-
while message = proc.output.gets
71-
if message.includes?("Installing") || message.includes?("Using")
72-
deps << message.split(' ', 3)[1]
73-
end
74-
puts message
75-
end
76-
puts
77-
end
50+
reader, writer = IO.pipe(write_blocking: true)
51+
Shards::Log.backend = ::Log::IOBackend.new writer
7852

79-
if $?.success?
80-
if deps.empty? || (options.has?("skip-executables") && options.has?("skip-postinstall"))
81-
success "Install completed in #{format_time(Time.monotonic - start)}"
82-
return
83-
end
53+
if options.has? "production"
54+
Shards.frozen = true
55+
Shards.with_development = false
8456
else
85-
fatal "Install failed (#{format_time(Time.monotonic - start)})"
57+
Shards.frozen = options.has? "frozen"
58+
Shards.with_development = !options.has?("without-development")
8659
end
8760

88-
shards = [] of Shard
89-
Dir.each_child("lib") do |child|
90-
next if child.starts_with? '.'
91-
next unless Shard.exists? child
61+
Shards.skip_executables = options.has? "skip-executables"
62+
Shards.jobs = options.get("jobs").to_i32 if options.has?("jobs")
63+
Shards.local = options.has? "local"
64+
Shards.skip_postinstall = options.has? "skip-postinstall"
9265

93-
shards << Shard.load child
94-
rescue YAML::ParseException
95-
warn "Failed to parse shard.yml contents for '#{child}'"
96-
end
97-
98-
unless options.has? "skip-postinstall"
99-
shards.select(&.has_postinstall?).each do |shard|
100-
if script = shard.find_target_script "postinstall", Geode::HOST_PLATFORM
101-
run_postinstall shard.name, script
102-
else
103-
warn "No postinstall script available for this platform"
104-
end
105-
end
106-
end
107-
108-
unless options.has? "skip-executables"
109-
Dir.mkdir_p "bin"
110-
111-
shards.reject(&.executables.empty?).each do |shard|
112-
shard.executables.each do |exe|
113-
src = Path["lib"] / shard.name / "bin" / exe
114-
115-
{% if flag?(:win32) %}
116-
unless File.exists?(src) || exe.ends_with?(".exe")
117-
src = Path[src.basename, exe + ".exe"]
118-
end
119-
{% end %}
120-
121-
unless File.exists? src
122-
warn "Executable '#{exe}' not found for #{shard.name}"
123-
next
124-
end
125-
126-
dest = Path["bin", exe].expand
127-
unless File.exists? dest
128-
begin
129-
File.copy src, dest
130-
info "Added executable '#{exe}'"
131-
rescue
132-
error "Failed to link #{shard.name} executable '#{exe}'"
133-
end
134-
end
66+
spawn do
67+
while input = reader.gets
68+
if input.starts_with? "Fetching"
69+
uri = URI.parse input.split(' ')[1]
70+
stdout << "" << uri.path << " (" << uri.hostname << ")\n"
13571
end
13672
end
13773
end
13874

75+
start = Time.monotonic
76+
Shards::Commands::WrapInstall.new(stdout, stderr).run
13977
success "Install completed in #{format_time(Time.monotonic - start)}"
14078
end
14179

src/geode.cr

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@ require "colorize"
44
require "file_utils"
55
require "license"
66
require "lua"
7+
require "shards/commands/install"
78
require "trigram"
89
require "wait_group"
910
require "yaml"
1011

1112
require "./commands/*"
1213
require "./config"
1314
require "./shard"
15+
require "./shards/base"
16+
require "./shards/install"
1417

1518
Colorize.on_tty_only!
1619

src/shards/base.cr

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
module Geode
2+
class WrapBase < Shards::Command
3+
@stdout : IO
4+
@stderr : IO
5+
6+
def initialize(@stdout : IO, @stderr : IO)
7+
super Dir.current
8+
end
9+
10+
protected def puts : Nil
11+
@stdout.puts
12+
end
13+
14+
protected def puts(msg : String) : Nil
15+
@stdout.puts msg
16+
end
17+
18+
protected def info(msg : String) : Nil
19+
@stdout << "» " << msg << '\n'
20+
end
21+
22+
protected def success(msg : String) : Nil
23+
@stdout << "» Success".colorize.green << ": " << msg << '\n'
24+
end
25+
26+
protected def warn(msg : String) : Nil
27+
@stdout << "» Warning".colorize.yellow << ": " << msg << '\n'
28+
end
29+
30+
protected def warn(*args : String) : Nil
31+
@stdout << "» Warning".colorize.yellow << ": " << args[0] << '\n'
32+
args[1..].each { |arg| @stdout << "» ".colorize.yellow << arg << '\n' }
33+
end
34+
35+
protected def error(msg : String) : Nil
36+
@stderr << "» Error".colorize.red << ": " << msg << '\n'
37+
end
38+
39+
protected def error(*args : String) : Nil
40+
@stderr << "» Error".colorize.red << ": " << args[0] << '\n'
41+
args[1..].each { |arg| @stderr << "» ".colorize.red << arg << '\n' }
42+
end
43+
44+
protected def fatal(*args : String) : NoReturn
45+
error *args
46+
raise Cling::ExitProgram.new 1
47+
end
48+
end
49+
end

src/shards/install.cr

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
module Shards::Commands
2+
class WrapInstall < Geode::WrapBase
3+
def run : Nil
4+
if Shards.frozen? && !lockfile?
5+
fatal "A shard.lock file was not found (needed for frozen install)"
6+
end
7+
8+
check_symlink_privilege
9+
info "Resolving dependencies"
10+
11+
solver = MolinilloSolver.new(spec, override)
12+
solver.locks = locks.shards if lockfile?
13+
14+
solver.prepare(development: Shards.with_development?)
15+
packages = handle_resolver_errors { solver.solve }
16+
17+
validate packages if Shards.frozen?
18+
install packages
19+
20+
if generate_lockfile? packages
21+
write_lockfile packages
22+
elsif !Shards.frozen?
23+
File.touch lockfile_path
24+
end
25+
26+
touch_install_path
27+
check_crystal_version packages
28+
end
29+
30+
private def validate(packages)
31+
packages.each do |package|
32+
if lock = locks.shards.find { |d| d.name == package.name }
33+
if lock.resolver != package.resolver
34+
fatal "#{package.name} source changed"
35+
else
36+
validate_locked_version(package, lock.version)
37+
end
38+
else
39+
fatal "Can't install new dependency #{package.name} in production"
40+
end
41+
end
42+
end
43+
44+
private def validate_locked_version(package, version)
45+
return if package.version == version
46+
fatal "#{package.name} requirements changed"
47+
end
48+
49+
private def install(packages : Array(Package))
50+
# packages are returned by the solver in reverse topological order,
51+
# so transitive dependencies are installed first
52+
packages.each do |package|
53+
# first install the dependency:
54+
next unless install package
55+
56+
# then execute the postinstall script
57+
# (with access to all transitive dependencies):
58+
package.postinstall
59+
60+
# always install executables because the path resolver never actually
61+
# installs dependencies:
62+
package.install_executables
63+
end
64+
end
65+
66+
private def install(package : Package)
67+
if package.installed?
68+
info "Using #{package.name} (#{package.report_version})"
69+
return
70+
end
71+
72+
info "Installing #{package.name} (#{package.report_version})"
73+
package.install
74+
package
75+
end
76+
77+
private def generate_lockfile?(packages)
78+
!Shards.frozen? && (!lockfile? || outdated_lockfile?(packages))
79+
end
80+
81+
private def outdated_lockfile?(packages)
82+
return true if locks.version != Shards::Lock::CURRENT_VERSION
83+
return true if packages.size != locks.shards.size
84+
85+
packages.index_by(&.name) != locks.shards.index_by(&.name)
86+
end
87+
end
88+
end

0 commit comments

Comments
 (0)