Merge pull request #9525 from chrisroberts/wsl-update
Support updated WSL implementation
This commit is contained in:
commit
ae54756d1d
|
@ -23,6 +23,7 @@ tags
|
|||
test/tmp/
|
||||
vendor/
|
||||
/exec
|
||||
.ruby-bundle
|
||||
|
||||
# Documentation
|
||||
_site/*
|
||||
|
|
|
@ -947,5 +947,9 @@ module Vagrant
|
|||
class WSLVirtualBoxWindowsAccessError < VagrantError
|
||||
error_key(:wsl_virtualbox_windows_access)
|
||||
end
|
||||
|
||||
class WSLRootFsNotFoundError < VagrantError
|
||||
error_key(:wsl_rootfs_not_found_error)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
require "rbconfig"
|
||||
require "shellwords"
|
||||
require "tempfile"
|
||||
require "tmpdir"
|
||||
require "log4r"
|
||||
|
||||
require "vagrant/util/subprocess"
|
||||
require "vagrant/util/powershell"
|
||||
|
@ -291,6 +293,87 @@ module Vagrant
|
|||
wsl? && !path.to_s.downcase.start_with?("/mnt/")
|
||||
end
|
||||
|
||||
# Compute the path to rootfs of currently active WSL.
|
||||
#
|
||||
# @return [String] A path to rootfs of a current WSL instance.
|
||||
def wsl_rootfs
|
||||
return @_wsl_rootfs if defined?(@_wsl_rootfs)
|
||||
|
||||
if wsl?
|
||||
# Mark our filesystem with a temporary file having an unique name.
|
||||
marker = Tempfile.new(Time.now.to_i.to_s)
|
||||
logger = Log4r::Logger.new("vagrant::util::platform::wsl")
|
||||
|
||||
# Check for lxrun installation first
|
||||
lxrun_path = [wsl_windows_appdata_local, "lxss"].join("\\")
|
||||
paths = [lxrun_path]
|
||||
|
||||
logger.debug("checking registry for WSL installation path")
|
||||
paths += PowerShell.execute_cmd(
|
||||
'(Get-ChildItem HKCU:\Software\Microsoft\Windows\CurrentVersion\Lxss ' \
|
||||
'| ForEach-Object {Get-ItemProperty $_.PSPath}).BasePath').to_s.split("\r\n").map(&:strip)
|
||||
paths.delete_if{|path| path.to_s.empty?}
|
||||
|
||||
paths.each do |path|
|
||||
# Lowercase the drive letter, skip the next symbol (which is a
|
||||
# colon from a Windows path) and convert path to UNIX style.
|
||||
check_path = "/mnt/#{path[0, 1].downcase}#{path[2..-1].tr('\\', '/')}/rootfs"
|
||||
|
||||
logger.debug("checking `#{path}` for current WSL instance")
|
||||
begin
|
||||
# https://blogs.msdn.microsoft.com/wsl/2016/06/15/wsl-file-system-support
|
||||
# Current WSL instance doesn't have an access to its mount from
|
||||
# within itself despite all others are available. That's the
|
||||
# hacky way we're using to determine current instance.
|
||||
# For example we have three WSL instances:
|
||||
# A -> C:\User\USER\AppData\Local\Packages\A\LocalState\rootfs
|
||||
# B -> C:\User\USER\AppData\Local\Packages\B\LocalState\rootfs
|
||||
# C -> C:\User\USER\AppData\Local\Packages\C\LocalState\rootfs
|
||||
# If we're in "A" WSL at the moment, then its path will not be
|
||||
# accessible since it's mounted for exactly the instance we're
|
||||
# in. All others can be opened.
|
||||
Dir.open(check_path) do |fs|
|
||||
# A fallback for a case if our trick will stop working. For
|
||||
# that we've created a temporary file with an unique name in
|
||||
# a current WSL and now seeking it among all WSL.
|
||||
if File.exist?("#{fs.path}/#{marker.path}")
|
||||
@_wsl_rootfs = path
|
||||
break
|
||||
end
|
||||
end
|
||||
rescue Errno::EACCES
|
||||
@_wsl_rootfs = path
|
||||
# You can create and simultaneously run multiple WSL instances,
|
||||
# comment out the "break", run this script within each one and
|
||||
# it'll return only single value.
|
||||
break
|
||||
rescue Errno::ENOENT
|
||||
# Warn about data discrepancy between Winreg and file system
|
||||
# states. For the sake of justice, it's worth mentioning that
|
||||
# it is possible only when someone will manually break WSL by
|
||||
# removing a directory of its base path (kinda "stupid WSL
|
||||
# uninstallation by removing hidden and system directory").
|
||||
logger.warn("WSL instance at `#{path} is broken or no longer exists")
|
||||
end
|
||||
# All other exceptions have to be raised since they will mean
|
||||
# something unpredictably terrible.
|
||||
end
|
||||
|
||||
marker.close!
|
||||
|
||||
raise Vagrant::Errors::WSLRootFsNotFoundError if @_wsl_rootfs.nil?
|
||||
end
|
||||
|
||||
# Attach the rootfs leaf to the path
|
||||
if @_wsl_rootfs != lxrun_path
|
||||
@_wsl_rootfs = "#{@_wsl_rootfs}\\rootfs"
|
||||
end
|
||||
|
||||
logger.debug("detected `#{@_wsl_rootfs}` as current WSL instance")
|
||||
|
||||
@_wsl_rootfs
|
||||
end
|
||||
|
||||
# Convert a WSL path to the local Windows path. This is useful
|
||||
# for conversion when calling out to Windows executables from
|
||||
# the WSL
|
||||
|
@ -298,11 +381,17 @@ module Vagrant
|
|||
# @param [String, Pathname] path Path to convert
|
||||
# @return [String]
|
||||
def wsl_to_windows_path(path)
|
||||
if wsl? && wsl_windows_access?
|
||||
if wsl? && wsl_windows_access? && !path.match(/^[a-zA-Z]:/)
|
||||
if wsl_path?(path)
|
||||
parts = path.split("/")
|
||||
parts.delete_if(&:empty?)
|
||||
[wsl_windows_appdata_local, "lxss", *parts].join("\\")
|
||||
root_path = wsl_rootfs
|
||||
# lxrun splits home separate so we need to account
|
||||
# for it's specialness here when we build the path
|
||||
if root_path.end_with?("lxss") && parts.first != "home"
|
||||
root_path = "#{root_path}\\rootfs"
|
||||
end
|
||||
[root_path, *parts].join("\\")
|
||||
else
|
||||
path = path.to_s.sub("/mnt/", "")
|
||||
parts = path.split("/")
|
||||
|
|
|
@ -13,12 +13,27 @@ module Vagrant
|
|||
MINIMUM_REQUIRED_VERSION = 3
|
||||
LOGGER = Log4r::Logger.new("vagrant::util::powershell")
|
||||
|
||||
# @return [String|nil] a powershell executable, depending on environment
|
||||
def self.executable
|
||||
if !defined?(@_powershell_executable)
|
||||
@_powershell_executable = "powershell"
|
||||
|
||||
# Try to use WSL interoperability if PowerShell is not symlinked to
|
||||
# the container.
|
||||
if Which.which(@_powershell_executable).nil? && Platform.wsl?
|
||||
@_powershell_executable += ".exe"
|
||||
|
||||
if Which.which(@_powershell_executable).nil?
|
||||
@_powershell_executable = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
@_powershell_executable
|
||||
end
|
||||
|
||||
# @return [Boolean] powershell executable available on PATH
|
||||
def self.available?
|
||||
if !defined?(@_powershell_available)
|
||||
@_powershell_available = !!Which.which("powershell")
|
||||
end
|
||||
@_powershell_available
|
||||
!executable.nil?
|
||||
end
|
||||
|
||||
# Execute a powershell script.
|
||||
|
@ -27,12 +42,11 @@ module Vagrant
|
|||
# @return [Subprocess::Result]
|
||||
def self.execute(path, *args, **opts, &block)
|
||||
validate_install!
|
||||
|
||||
if opts.delete(:sudo) || opts.delete(:runas)
|
||||
powerup_command(path, args, opts)
|
||||
else
|
||||
command = [
|
||||
"powershell",
|
||||
executable,
|
||||
"-NoLogo",
|
||||
"-NoProfile",
|
||||
"-NonInteractive",
|
||||
|
@ -57,7 +71,7 @@ module Vagrant
|
|||
def self.execute_cmd(command)
|
||||
validate_install!
|
||||
c = [
|
||||
"powershell",
|
||||
executable,
|
||||
"-NoLogo",
|
||||
"-NoProfile",
|
||||
"-NonInteractive",
|
||||
|
@ -77,7 +91,7 @@ module Vagrant
|
|||
def self.version
|
||||
if !defined?(@_powershell_version)
|
||||
command = [
|
||||
"powershell",
|
||||
executable,
|
||||
"-NoLogo",
|
||||
"-NoProfile",
|
||||
"-NonInteractive",
|
||||
|
|
|
@ -1629,6 +1629,10 @@ en:
|
|||
Linux, please refer to the Vagrant documentation:
|
||||
|
||||
https://www.vagrantup.com/docs/other/wsl.html
|
||||
wsl_rootfs_not_found_error: |-
|
||||
Vagrant is unable to determine the location of this instance of the Windows
|
||||
Subsystem for Linux. If this error persists it may be resolved by destroying
|
||||
this subsystem and installing it again.
|
||||
#-------------------------------------------------------------------------------
|
||||
# Translations for config validation errors
|
||||
#-------------------------------------------------------------------------------
|
||||
|
|
|
@ -4,8 +4,7 @@ require "vagrant/util/platform"
|
|||
|
||||
describe Vagrant::Util::Platform do
|
||||
include_context "unit"
|
||||
|
||||
|
||||
after{ described_class.reset! }
|
||||
subject { described_class }
|
||||
|
||||
describe "#cygwin_path" do
|
||||
|
@ -55,11 +54,6 @@ describe Vagrant::Util::Platform do
|
|||
describe "#cygwin?" do
|
||||
before do
|
||||
allow(subject).to receive(:platform).and_return("test")
|
||||
described_class.reset!
|
||||
end
|
||||
|
||||
after do
|
||||
described_class.reset!
|
||||
end
|
||||
|
||||
around do |example|
|
||||
|
@ -99,11 +93,6 @@ describe Vagrant::Util::Platform do
|
|||
describe "#msys?" do
|
||||
before do
|
||||
allow(subject).to receive(:platform).and_return("test")
|
||||
described_class.reset!
|
||||
end
|
||||
|
||||
after do
|
||||
described_class.reset!
|
||||
end
|
||||
|
||||
around do |example|
|
||||
|
@ -162,7 +151,6 @@ describe Vagrant::Util::Platform do
|
|||
|
||||
describe ".systemd?" do
|
||||
before{ allow(subject).to receive(:windows?).and_return(false) }
|
||||
after{ subject.reset! }
|
||||
|
||||
context "on windows" do
|
||||
before{ expect(subject).to receive(:windows?).and_return(true) }
|
||||
|
@ -223,10 +211,142 @@ describe Vagrant::Util::Platform do
|
|||
end
|
||||
|
||||
it "should return false if disabled" do
|
||||
Vagrant::Util::Platform.reset!
|
||||
allow(Vagrant::Util::PowerShell).to receive(:execute_cmd).and_return('Disabled')
|
||||
|
||||
expect(Vagrant::Util::Platform.windows_hyperv_enabled?).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
context "within the WSL" do
|
||||
before{ allow(subject).to receive(:wsl?).and_return(true) }
|
||||
|
||||
describe ".wsl_path?" do
|
||||
it "should return true when path is not within /mnt" do
|
||||
expect(subject.wsl_path?("/tmp")).to be(true)
|
||||
end
|
||||
|
||||
it "should return false when path is within /mnt" do
|
||||
expect(subject.wsl_path?("/mnt/c")).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe ".wsl_rootfs" do
|
||||
let(:appdata_path){ "C:\\Custom\\Path" }
|
||||
let(:registry_paths){ nil }
|
||||
|
||||
before do
|
||||
allow(subject).to receive(:wsl_windows_appdata_local).and_return(appdata_path)
|
||||
allow(Tempfile).to receive(:new).and_return(double("tempfile", path: "file.path", close!: true))
|
||||
allow(Vagrant::Util::PowerShell).to receive(:execute_cmd).and_return(registry_paths)
|
||||
end
|
||||
|
||||
context "when no instance information is in the registry" do
|
||||
before do
|
||||
expect(Dir).to receive(:open).with(/.*Custom.*Path.*/).and_yield(double("path", path: appdata_path))
|
||||
expect(File).to receive(:exist?).and_return(true)
|
||||
end
|
||||
|
||||
it "should only check the lxrun path" do
|
||||
expect(subject.wsl_rootfs).to include(appdata_path)
|
||||
end
|
||||
end
|
||||
|
||||
context "with instance information in the registry" do
|
||||
let(:registry_paths) { ["C:\\Path1", "C:\\Path2"].join("\r\n") }
|
||||
|
||||
before do
|
||||
allow(Dir).to receive(:open).and_yield(double("path", path: appdata_path))
|
||||
allow(File).to receive(:exist?).and_return(false)
|
||||
end
|
||||
|
||||
context "when no matches are detected" do
|
||||
it "should check all paths given" do
|
||||
expect(Dir).to receive(:open).and_yield(double("path", path: appdata_path)).exactly(3).times
|
||||
expect(File).to receive(:exist?).and_return(false).exactly(3).times
|
||||
expect{ subject.wsl_rootfs }.to raise_error(Vagrant::Errors::WSLRootFsNotFoundError)
|
||||
end
|
||||
|
||||
it "should raise not found error" do
|
||||
expect{ subject.wsl_rootfs }.to raise_error(Vagrant::Errors::WSLRootFsNotFoundError)
|
||||
end
|
||||
end
|
||||
|
||||
context "when file marker match found" do
|
||||
let(:matching_path){ registry_paths.split("\r\n").last }
|
||||
let(:matching_part){ matching_path.split("\\").last }
|
||||
|
||||
before do
|
||||
allow(File).to receive(:exist?).with(/#{matching_part}/).and_return(true)
|
||||
end
|
||||
|
||||
it "should return the matching path" do
|
||||
expect(Dir).to receive(:open).with(/#{matching_part}/).and_yield(double("path", path: matching_part))
|
||||
expect(subject.wsl_rootfs).to start_with(matching_path)
|
||||
end
|
||||
|
||||
it "should return matching path when access error encountered" do
|
||||
expect(Dir).to receive(:open).with(/#{matching_part}/).and_raise(Errno::EACCES)
|
||||
expect(subject.wsl_rootfs).to start_with(matching_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".wsl_to_windows_path" do
|
||||
let(:path){ "/home/vagrant/test" }
|
||||
|
||||
context "when not within WSL" do
|
||||
before{ allow(subject).to receive(:wsl?).and_return(false) }
|
||||
|
||||
it "should return the path unmodified" do
|
||||
expect(subject.wsl_to_windows_path(path)).to eq(path)
|
||||
end
|
||||
end
|
||||
|
||||
context "when within WSL" do
|
||||
before{ allow(subject).to receive(:wsl?).and_return(true) }
|
||||
|
||||
context "when windows access is not enabled" do
|
||||
before{ allow(subject).to receive(:wsl_windows_access?).and_return(false) }
|
||||
|
||||
it "should return the path unmodified" do
|
||||
expect(subject.wsl_to_windows_path(path)).to eq(path)
|
||||
end
|
||||
end
|
||||
|
||||
context "when windows access is enabled" do
|
||||
let(:rootfs_path){ "C:\\WSL\\rootfs" }
|
||||
|
||||
before do
|
||||
allow(subject).to receive(:wsl_windows_access?).and_return(true)
|
||||
allow(subject).to receive(:wsl_rootfs).and_return(rootfs_path)
|
||||
end
|
||||
|
||||
it "should generate expanded path when within WSL" do
|
||||
expect(subject.wsl_to_windows_path(path)).to eq("#{rootfs_path}#{path.gsub("/", "\\")}")
|
||||
end
|
||||
|
||||
it "should generate direct path when outside the WSL" do
|
||||
expect(subject.wsl_to_windows_path("/mnt/c/vagrant")).to eq("c:\\vagrant")
|
||||
end
|
||||
|
||||
it "should not modify path when already in windows format" do
|
||||
expect(subject.wsl_to_windows_path("C:\\vagrant")).to eq("C:\\vagrant")
|
||||
end
|
||||
|
||||
context "when within lxrun generated WSL instance" do
|
||||
let(:rootfs_path){ "C:\\WSL\\lxss" }
|
||||
|
||||
it "should not include rootfs when accessing home" do
|
||||
expect(subject.wsl_to_windows_path("/home/vagrant")).not_to include("rootfs")
|
||||
end
|
||||
|
||||
it "should include rootfs when accessing non-home path" do
|
||||
expect(subject.wsl_to_windows_path("/tmp/test")).to include("rootfs")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -55,6 +55,7 @@ Gem::Specification.new do |s|
|
|||
all_files = Dir.chdir(root_path) { Dir.glob("**/{*,.*}") }
|
||||
all_files.reject! { |file| [".", ".."].include?(File.basename(file)) }
|
||||
all_files.reject! { |file| file.start_with?("website/") }
|
||||
all_files.reject! { |file| file.start_with?("test/") }
|
||||
gitignore_path = File.join(root_path, ".gitignore")
|
||||
gitignore = File.readlines(gitignore_path)
|
||||
gitignore.map! { |line| line.chomp.strip }
|
||||
|
|
Loading…
Reference in New Issue