@@ -6,6 +6,13 @@ module ArduinoCI
6
6
7
7
# Tools for interacting with the host machine
8
8
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
+
9
16
# Cross-platform way of finding an executable in the $PATH.
10
17
# via https://stackoverflow.com/a/5471032/2063546
11
18
# which('ruby') #=> /usr/bin/ruby
@@ -38,21 +45,63 @@ def self.os
38
45
return :windows if OS . windows?
39
46
end
40
47
48
+ # Cross-platform symlinking
41
49
# if on windows, call mklink, else self.symlink
42
50
# @param [Pathname] old_path
43
51
# @param [Pathname] new_path
44
52
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
46
55
47
- # https://stackoverflow.com/a/22716582/2063546
56
+ # via https://stackoverflow.com/a/22716582/2063546
48
57
# windows mklink syntax is reverse of unix ln -s
49
58
# windows mklink is built into cmd.exe
50
59
# 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 )
53
62
54
63
_stdout , _stderr , exitstatus = Open3 . capture3 ( 'cmd.exe' , "/C mklink /D #{ np } #{ orp } " )
55
64
exitstatus . success?
56
65
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
57
106
end
58
107
end
0 commit comments