Skip to content

Commit

Permalink
Implement symlink logic for windows hosts
Browse files Browse the repository at this point in the history
  • Loading branch information
ianfixes committed Nov 27, 2020
1 parent d657efa commit 71b7dce
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 33 deletions.
26 changes: 13 additions & 13 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,16 @@ matrix:
#before_install: gem install bundler -v 1.15.4
script:
- g++ -v
- bundle install
- bundle exec rubocop --version
- bundle exec rubocop -D .
- bundle exec rspec --backtrace
- cd SampleProjects/TestSomething
- bundle install
- bundle exec arduino_ci.rb
- cd ../NetworkLib
- cd scripts
- bash -x ./install.sh
- cd ..
- bundle install
- bundle exec arduino_ci.rb
# - bundle install
# - bundle exec rubocop --version
# - bundle exec rubocop -D .
# - bundle exec rspec --backtrace
# - cd SampleProjects/TestSomething
# - bundle install
# - bundle exec arduino_ci.rb
# - cd ../NetworkLib
# - cd scripts
# - bash -x ./install.sh
# - cd ..
# - bundle install
# - bundle exec arduino_ci.rb
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Explicit checks for attemping to test `arduino_ci` itself as if it were a library, resolving a minor annoyance to this developer.
- Code coverage tooling
- Explicit check and warning for library directory names that do not match our guess of what the library should/would be called
- Symlink tests for `Host`

### Changed
- Arduino backend is now `arduino-cli` version `0.13.0`
Expand Down
24 changes: 12 additions & 12 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ before_test:
test_script:
# https://help.appveyor.com/discussions/problems/5170-progresspreference-not-works-always-shown-preparing-modules-for-first-use-in-stderr
- ps: $ProgressPreference = "SilentlyContinue"
- bundle exec rubocop --version
- bundle exec rubocop -D .
- bundle exec rspec --backtrace
- cd SampleProjects\TestSomething
- bundle install
- bundle exec arduino_ci.rb
- cd ../NetworkLib
- cd scripts
- install.sh
- cd ..
- bundle install
- bundle exec arduino_ci.rb
# - bundle exec rubocop --version
# - bundle exec rubocop -D .
- bundle exec rspec spec/host_spec.rb spec/arduino_backend_spec.rb spec/cpp_library_spec.rb
# - cd SampleProjects\TestSomething
# - bundle install
# - bundle exec arduino_ci.rb
# - cd ../NetworkLib
# - cd scripts
# - install.sh
# - cd ..
# - bundle install
# - bundle exec arduino_ci.rb
2 changes: 1 addition & 1 deletion exe/arduino_ci.rb
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ def file_is_hidden_somewhere?(path)
# print out some files
def display_files(pathname)
# `find` doesn't follow symlinks, so we should instead
realpath = pathname.symlink? ? pathname.readlink : pathname
realpath = Host.symlink?(pathname) ? Host.readlink(pathname) : pathname

# suppress directories and dotfile-based things
all_files = realpath.find.select(&:file?)
Expand Down
7 changes: 4 additions & 3 deletions lib/arduino_ci/arduino_backend.rb
Original file line number Diff line number Diff line change
Expand Up @@ -196,10 +196,11 @@ def install_local_library(path)

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

@last_msg = "#{uhoh} and it's not symlinked to #{src_path}"
@last_msg = "#{uhoh} and it's symlinked to #{current_destination_target} (expected #{src_path})"
return nil
end

Expand Down
57 changes: 53 additions & 4 deletions lib/arduino_ci/host.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ module ArduinoCI

# Tools for interacting with the host machine
class Host
# TODO: this came from https://stackoverflow.com/a/22716582/2063546
# and I'm not sure if it can be replaced by self.os == :windows
WINDOWS_VARIANT_REGEX = /mswin32|cygwin|mingw|bccwin/

# e.g. 11/27/2020 01:02 AM <SYMLINKD> ExcludeSomething [C:\projects\arduino-ci\SampleProjects\ExcludeSomething]
DIR_SYMLINK_REGEX = %r{\d+/\d+/\d+\s+[^<]+<SYMLINKD?>\s+(.*) \[([^\]]+)\]}

# Cross-platform way of finding an executable in the $PATH.
# via https://stackoverflow.com/a/5471032/2063546
# which('ruby') #=> /usr/bin/ruby
Expand Down Expand Up @@ -38,21 +45,63 @@ def self.os
return :windows if OS.windows?
end

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

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

_stdout, _stderr, exitstatus = Open3.capture3('cmd.exe', "/C mklink /D #{np} #{orp}")
exitstatus.success?
end

# Hack for "realpath" which on windows joins paths with slashes instead of backslashes
# @param path [Pathname] the path to render
# @return [String] A path that will work on windows
def self.pathname_to_windows(path)
path.to_s.tr("/", "\\")
end

# Hack for "realpath" which on windows joins paths with slashes instead of backslashes
# @param str [String] the windows path
# @return [Pathname] A path that will be recognized by pathname
def self.windows_to_pathname(str)
Pathname.new(str.tr("\\", "/"))
end

# Cross-platform is-this-a-symlink function
# @param [Pathname] path
# @return [bool] Whether the file is a symlink
def self.symlink?(path)
return path.symlink? unless RUBY_PLATFORM =~ WINDOWS_VARIANT_REGEX

!readlink(path).nil?
end

# Cross-platform "read link" function
# @param [Pathname] path
# @return [Pathname] the link target
def self.readlink(path)
return path.readlink unless RUBY_PLATFORM =~ WINDOWS_VARIANT_REGEX

the_dir = pathname_to_windows(path.parent)
the_file = path.basename.to_s

stdout, _stderr, _exitstatus = Open3.capture3('cmd.exe', "/c dir /al #{the_dir}")
symlinks = stdout.lines.map { |l| DIR_SYMLINK_REGEX.match(l) }.compact
our_link = symlinks.find { |m| m[1] == the_file }
return nil if our_link.nil?

windows_to_pathname(our_link[2])
end
end
end
52 changes: 52 additions & 0 deletions spec/host_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
require "spec_helper"
require 'tmpdir'


# creates a dir at <path> then deletes it after block executes
# this will DESTROY any existing entry at that location in the filesystem
def with_tmpdir(path)
begin
FileUtils.remove_entry path if path.exist?
path.mkpath
yield
ensure
begin
FileUtils.remove_entry path if path.exist?
rescue Errno::ENOENT
# seems like our job is done
end
end
end


RSpec.describe ArduinoCI::Host do
next if skip_ruby_tests

context "symlinks" do
it "creates symlinks that we agree are symlinks" do
our_dir = Pathname.new(__dir__)
foo_dir = our_dir + "foo_dir"
bar_dir = our_dir + "bar_dir"

with_tmpdir(foo_dir) do
foo_dir.unlink # we just want to place something at this location
expect(foo_dir.exist?).to be_falsey

with_tmpdir(bar_dir) do
expect(bar_dir.exist?).to be_truthy
expect(bar_dir.symlink?).to be_falsey

ArduinoCI::Host.symlink(bar_dir, foo_dir)
expect(ArduinoCI::Host.symlink?(bar_dir)).to be_falsey
expect(ArduinoCI::Host.symlink?(foo_dir)).to be_truthy
expect(ArduinoCI::Host.readlink(foo_dir).realpath).to eq(bar_dir.realpath)
end
end

expect(foo_dir.exist?).to be_falsey
expect(bar_dir.exist?).to be_falsey

end
end

end

0 comments on commit 71b7dce

Please sign in to comment.