Skip to content

Commit 71b7dce

Browse files
committed
Implement symlink logic for windows hosts
1 parent d657efa commit 71b7dce

File tree

7 files changed

+136
-33
lines changed

7 files changed

+136
-33
lines changed

Diff for: .travis.yml

+13-13
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,16 @@ matrix:
2020
#before_install: gem install bundler -v 1.15.4
2121
script:
2222
- g++ -v
23-
- bundle install
24-
- bundle exec rubocop --version
25-
- bundle exec rubocop -D .
26-
- bundle exec rspec --backtrace
27-
- cd SampleProjects/TestSomething
28-
- bundle install
29-
- bundle exec arduino_ci.rb
30-
- cd ../NetworkLib
31-
- cd scripts
32-
- bash -x ./install.sh
33-
- cd ..
34-
- bundle install
35-
- bundle exec arduino_ci.rb
23+
# - bundle install
24+
# - bundle exec rubocop --version
25+
# - bundle exec rubocop -D .
26+
# - bundle exec rspec --backtrace
27+
# - cd SampleProjects/TestSomething
28+
# - bundle install
29+
# - bundle exec arduino_ci.rb
30+
# - cd ../NetworkLib
31+
# - cd scripts
32+
# - bash -x ./install.sh
33+
# - cd ..
34+
# - bundle install
35+
# - bundle exec arduino_ci.rb

Diff for: CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1111
- Explicit checks for attemping to test `arduino_ci` itself as if it were a library, resolving a minor annoyance to this developer.
1212
- Code coverage tooling
1313
- Explicit check and warning for library directory names that do not match our guess of what the library should/would be called
14+
- Symlink tests for `Host`
1415

1516
### Changed
1617
- Arduino backend is now `arduino-cli` version `0.13.0`

Diff for: appveyor.yml

+12-12
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,15 @@ before_test:
1919
test_script:
2020
# https://help.appveyor.com/discussions/problems/5170-progresspreference-not-works-always-shown-preparing-modules-for-first-use-in-stderr
2121
- ps: $ProgressPreference = "SilentlyContinue"
22-
- bundle exec rubocop --version
23-
- bundle exec rubocop -D .
24-
- bundle exec rspec --backtrace
25-
- cd SampleProjects\TestSomething
26-
- bundle install
27-
- bundle exec arduino_ci.rb
28-
- cd ../NetworkLib
29-
- cd scripts
30-
- install.sh
31-
- cd ..
32-
- bundle install
33-
- bundle exec arduino_ci.rb
22+
# - bundle exec rubocop --version
23+
# - bundle exec rubocop -D .
24+
- bundle exec rspec spec/host_spec.rb spec/arduino_backend_spec.rb spec/cpp_library_spec.rb
25+
# - cd SampleProjects\TestSomething
26+
# - bundle install
27+
# - bundle exec arduino_ci.rb
28+
# - cd ../NetworkLib
29+
# - cd scripts
30+
# - install.sh
31+
# - cd ..
32+
# - bundle install
33+
# - bundle exec arduino_ci.rb

Diff for: exe/arduino_ci.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ def file_is_hidden_somewhere?(path)
157157
# print out some files
158158
def display_files(pathname)
159159
# `find` doesn't follow symlinks, so we should instead
160-
realpath = pathname.symlink? ? pathname.readlink : pathname
160+
realpath = Host.symlink?(pathname) ? Host.readlink(pathname) : pathname
161161

162162
# suppress directories and dotfile-based things
163163
all_files = realpath.find.select(&:file?)

Diff for: lib/arduino_ci/arduino_backend.rb

+4-3
Original file line numberDiff line numberDiff line change
@@ -196,10 +196,11 @@ def install_local_library(path)
196196

197197
uhoh = "There is already a library '#{library_name}' in the library directory (#{destination_path})"
198198
# maybe it's a symlink? that would be OK
199-
if destination_path.symlink?
200-
return cpp_library if destination_path.readlink == src_path
199+
if Host.symlink?(destination_path)
200+
current_destination_target = Host.readlink(destination_path)
201+
return cpp_library if current_destination_target == src_path
201202

202-
@last_msg = "#{uhoh} and it's not symlinked to #{src_path}"
203+
@last_msg = "#{uhoh} and it's symlinked to #{current_destination_target} (expected #{src_path})"
203204
return nil
204205
end
205206

Diff for: lib/arduino_ci/host.rb

+53-4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@ module ArduinoCI
66

77
# Tools for interacting with the host machine
88
class Host
9+
# TODO: this came from https://stackoverflow.com/a/22716582/2063546
10+
# and I'm not sure if it can be replaced by self.os == :windows
11+
WINDOWS_VARIANT_REGEX = /mswin32|cygwin|mingw|bccwin/
12+
13+
# e.g. 11/27/2020 01:02 AM <SYMLINKD> ExcludeSomething [C:\projects\arduino-ci\SampleProjects\ExcludeSomething]
14+
DIR_SYMLINK_REGEX = %r{\d+/\d+/\d+\s+[^<]+<SYMLINKD?>\s+(.*) \[([^\]]+)\]}
15+
916
# Cross-platform way of finding an executable in the $PATH.
1017
# via https://stackoverflow.com/a/5471032/2063546
1118
# which('ruby') #=> /usr/bin/ruby
@@ -38,21 +45,63 @@ def self.os
3845
return :windows if OS.windows?
3946
end
4047

48+
# Cross-platform symlinking
4149
# if on windows, call mklink, else self.symlink
4250
# @param [Pathname] old_path
4351
# @param [Pathname] new_path
4452
def self.symlink(old_path, new_path)
45-
return FileUtils.ln_s(old_path.to_s, new_path.to_s) unless RUBY_PLATFORM =~ /mswin32|cygwin|mingw|bccwin/
53+
# we would prefer `new_path.make_symlink(old_path)` but "symlink function is unimplemented on this machine" with windows
54+
return new_path.make_symlink(old_path) unless RUBY_PLATFORM =~ WINDOWS_VARIANT_REGEX
4655

47-
# https://stackoverflow.com/a/22716582/2063546
56+
# via https://stackoverflow.com/a/22716582/2063546
4857
# windows mklink syntax is reverse of unix ln -s
4958
# windows mklink is built into cmd.exe
5059
# vulnerable to command injection, but okay because this is a hack to make a cli tool work.
51-
orp = old_path.realpath.to_s.tr("/", "\\") # HACK DUE TO REALPATH BUG where it
52-
np = new_path.to_s.tr("/", "\\") # still joins windows paths with '/'
60+
orp = pathname_to_windows(old_path.realpath)
61+
np = pathname_to_windows(new_path)
5362

5463
_stdout, _stderr, exitstatus = Open3.capture3('cmd.exe', "/C mklink /D #{np} #{orp}")
5564
exitstatus.success?
5665
end
66+
67+
# Hack for "realpath" which on windows joins paths with slashes instead of backslashes
68+
# @param path [Pathname] the path to render
69+
# @return [String] A path that will work on windows
70+
def self.pathname_to_windows(path)
71+
path.to_s.tr("/", "\\")
72+
end
73+
74+
# Hack for "realpath" which on windows joins paths with slashes instead of backslashes
75+
# @param str [String] the windows path
76+
# @return [Pathname] A path that will be recognized by pathname
77+
def self.windows_to_pathname(str)
78+
Pathname.new(str.tr("\\", "/"))
79+
end
80+
81+
# Cross-platform is-this-a-symlink function
82+
# @param [Pathname] path
83+
# @return [bool] Whether the file is a symlink
84+
def self.symlink?(path)
85+
return path.symlink? unless RUBY_PLATFORM =~ WINDOWS_VARIANT_REGEX
86+
87+
!readlink(path).nil?
88+
end
89+
90+
# Cross-platform "read link" function
91+
# @param [Pathname] path
92+
# @return [Pathname] the link target
93+
def self.readlink(path)
94+
return path.readlink unless RUBY_PLATFORM =~ WINDOWS_VARIANT_REGEX
95+
96+
the_dir = pathname_to_windows(path.parent)
97+
the_file = path.basename.to_s
98+
99+
stdout, _stderr, _exitstatus = Open3.capture3('cmd.exe', "/c dir /al #{the_dir}")
100+
symlinks = stdout.lines.map { |l| DIR_SYMLINK_REGEX.match(l) }.compact
101+
our_link = symlinks.find { |m| m[1] == the_file }
102+
return nil if our_link.nil?
103+
104+
windows_to_pathname(our_link[2])
105+
end
57106
end
58107
end

Diff for: spec/host_spec.rb

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
require "spec_helper"
2+
require 'tmpdir'
3+
4+
5+
# creates a dir at <path> then deletes it after block executes
6+
# this will DESTROY any existing entry at that location in the filesystem
7+
def with_tmpdir(path)
8+
begin
9+
FileUtils.remove_entry path if path.exist?
10+
path.mkpath
11+
yield
12+
ensure
13+
begin
14+
FileUtils.remove_entry path if path.exist?
15+
rescue Errno::ENOENT
16+
# seems like our job is done
17+
end
18+
end
19+
end
20+
21+
22+
RSpec.describe ArduinoCI::Host do
23+
next if skip_ruby_tests
24+
25+
context "symlinks" do
26+
it "creates symlinks that we agree are symlinks" do
27+
our_dir = Pathname.new(__dir__)
28+
foo_dir = our_dir + "foo_dir"
29+
bar_dir = our_dir + "bar_dir"
30+
31+
with_tmpdir(foo_dir) do
32+
foo_dir.unlink # we just want to place something at this location
33+
expect(foo_dir.exist?).to be_falsey
34+
35+
with_tmpdir(bar_dir) do
36+
expect(bar_dir.exist?).to be_truthy
37+
expect(bar_dir.symlink?).to be_falsey
38+
39+
ArduinoCI::Host.symlink(bar_dir, foo_dir)
40+
expect(ArduinoCI::Host.symlink?(bar_dir)).to be_falsey
41+
expect(ArduinoCI::Host.symlink?(foo_dir)).to be_truthy
42+
expect(ArduinoCI::Host.readlink(foo_dir).realpath).to eq(bar_dir.realpath)
43+
end
44+
end
45+
46+
expect(foo_dir.exist?).to be_falsey
47+
expect(bar_dir.exist?).to be_falsey
48+
49+
end
50+
end
51+
52+
end

0 commit comments

Comments
 (0)