我有一个构建过程的一部分,在Windows中创建一个可怕的长路径。这不是我的错。这是几个目录深,没有一个目录名异常长;它们只是很长而且数量足以超过MAX_PATH
(260个字符)。我没有在这些名称中使用除ASCII以外的任何东西。
最大的问题是,在dist
目标期间,爆炸发生在Module::Build的内部深处,尽管我认为构建系统并不重要,因为它们会创建相同的目录
使用File::Path
创建其中一个过长的目录失败:
use File::Path qw( make_path );
make_path( 'C:\\.....' ); # fails if path is over 260 chars
同样,一旦绝对路径超过MAX_PATH
,手动构建每个目录级别就会失败。
这不是新的,不是Perl的错,而且Microsoft在Naming Files, Paths, and Namespaces中记录了它。他们的修复建议在任何路径前添加\\?\
以访问Unicode文件名API。但是,这似乎不是Perl脚本的完整修复,因为它仍然失败:
use File::Path qw( make_path );
make_path( '\\\\?\\C:\\.....' ); # still fails if path is over MAX_PATH, works otherwise
这可能是因为make_path
拉开了它的参数,然后逐个浏览目录,因此\\?\
仅适用于MAX_PATH
以内的顶级
我挖了一个bug report to ActiveState,表示我需要修改一些其他内容来获取Unicode文件名,而Jan Dubois在Re: "long" filenames on Windows 2K/XP中提供了更多细节,尽管我不确定它适用(并且非常古老)。 perlrun提到这种用法是-C
转换的工作,但显然这部分被放弃了。 perl RT队列有一个更新的错误60888: Win32: support full unicode in filenames (use Wide-system calls)。
Miyagawa notes some Unicode filename issues和Win32API::File没有特别提及长路径。然而,Win32API::File CPAN Forum entry似乎只表示恐惧,导致愤怒,导致仇恨,等等。 Perlmonks帖How to stat a file with a Unicode (UTF16-LE) filename in Windows?中有一个例子。似乎Win32::CreateDirectory
就是答案,下次我在Windows机器旁边时会尝试这样做。
然后,假设我可以创建长路径路径。现在我必须教它Module :: Build,也许还有其他东西来处理它。如果Win32::GetANSIPathName()
完成了它在锡上的说法,那么monkeypatches可能会很容易实现。
答案 0 :(得分:9)
以下脚本有效:它将字符串写入具有长路径的目录中的文件,并且能够读回相同的字符串。 (成功运行不会产生控制台输出)。我也做了一项繁琐的工作来覆盖open
。
#!/usr/bin/perl
use strict;
use warnings;
use Carp;
use Encode qw( encode );
use Symbol;
use Win32;
use Win32API::File qw(
CreateFileW OsFHandleOpen
FILE_GENERIC_READ FILE_GENERIC_WRITE
OPEN_EXISTING CREATE_ALWAYS FILE_SHARE_READ
);
use Win32::API;
use File::Spec::Functions qw(catfile);
Win32::API->Import(
Kernel32 => qq{BOOL CreateDirectoryW(LPWSTR lpPathNameW, VOID *p)}
);
my %modes = (
'<' => {
access => FILE_GENERIC_READ,
create => OPEN_EXISTING,
mode => 'r',
},
'>' => {
access => FILE_GENERIC_WRITE,
create => CREATE_ALWAYS,
mode => 'w',
},
# and the rest ...
);
use ex::override open => sub(*;$@) {
$_[0] = gensym;
my %mode = %{ $modes{$_[1]} };
my $os_fh = CreateFileW(
encode('UCS-2le', "$_[2]\0"),
$mode{access},
FILE_SHARE_READ,
[],
$mode{create},
0,
[],
) or do {$! = $^E; return };
OsFHandleOpen($_[0], $os_fh, $mode{mode}) or return;
return 1;
};
my $path = '\\\\?\\' . Win32::GetLongPathName($ENV{TEMP});
my @comps = ('0123456789') x 30;
my $dir = mk_long_dir($path, \@comps);
my $file = 'test.txt';
my $str = "This is a test\n";
write_test_file($dir, $file, $str);
$str eq read_test_file($dir, $file) or die "Read failure\n";
sub write_test_file {
my ($dir, $file, $str) = @_,
my $path = catfile $dir, $file;
open my $fh, '>', $path
or croak "Cannot open '$path':$!";
print $fh $str or die "Cannot print: $!";
close $fh or die "Cannot close: $!";
return;
}
sub read_test_file {
my ($dir, $file) = @_,
my $path = catfile $dir, $file;
open my $fh, '<', $path
or croak "Cannot open '$path': $!";
my $contents = do { local $/; <$fh> };
close $fh or die "Cannot close: $!";
return $contents;
}
sub mk_long_dir {
my ($path, $comps) = @_;
for my $comp ( @$comps ) {
$path = catfile $path, $comp;
my $ucs_path = encode('UCS-2le', "$path\0");
CreateDirectoryW($ucs_path, undef)
or croak "Failed to create directory: '$path': $^E";
}
return $path;
}
使用内置Win32::GetANSIPathName()
的{{1}}不起作用:返回的路径太长。
请参阅编辑失败实验的历史记录。
答案 1 :(得分:6)
以下代码实际上创建了非常深(超过260个字符长)的目录结构。至少在我的机器上:
use Win32::API;
$cd = Win32::API->new('kernel32', 'CreateDirectoryW', 'PP', 'N');
$dir = '\\\\?\\c:\\!experiments';
$res = 1;
do
{
print 'path length: ' . length($dir) . "\n";
$dirname = pack('S*', unpack('C*', "$dir\0")); #dirty way to produce UTF-16LE string
$res = $cd->Call($dirname, 0);
print "$res\n";
$dir .= '\\abcde';
} while ( $res );
答案 2 :(得分:2)
我知道这不是您特定问题的解决方案。然而,在很多场景中,能够将非常长的路径映射到驱动器号将允许人们回避问题,因此在处理非常长的路径名称时非常有用,而无需涉及大量的路径。 Windows特定代码和文档。
尽管我已经付出了很多努力来弄清楚如何做到这一点,但我还是会建议使用SUBST
。 Win32::FileOp提供Subst
和Unsubst
。然后,您可以将顶级工作目录映射到未使用的驱动器号(可以使用Substed
找到)。我会开始检查Z
并向后工作。
或者,您可以执行shell,调用subst
实用程序,不使用任何参数来获取当前替换列表,选择不存在的替换列表。
这些都不是完全安全的,因为替换可能会在构建过程中发生变化。
答案 3 :(得分:2)
这应该是一个评论,但在评论中发布代码几乎没用。
UNC路径也不起作用:
C:\> net share perlbuild e:\home\src
#!/usr/bin/perl
use strict;
use warnings;
use File::Path qw(make_path);
use File::Slurp;
use Path::Class;
my $top = dir('//Computer/perlbuild');
my @comps = ('0123456789') x 30;
my $path = dir($top, @comps);
make_path $path, { verbose => 1 };
my $file = file($path, 'test.txt');
write_file "$file" => 'This is a test';
print read_file "$file";
结果:
mkdir \\Computer\perlbuild\0123456789\0123456789\0123456789\0123456789\0123456 789\0123456789\0123456789\0123456789\0123456789\0123456789\0123456789\0123456789 \0123456789\0123456789\0123456789\0123456789\0123456789\0123456789\0123456789\01 23456789\0123456789: No such file or directory; The filename or extension is too long at C:\Temp\k.pl line 15
答案 4 :(得分:-1)
我有三个想法,所有这些都是黑客攻击:
从一些短目录名开始(C:\ data_directory \ a \ b \ c \ d \ 4 \ 5 \ 6 \ ...),然后重命名目录(当然首先从最深的目录开始) )。
创建适合中等长路径的Windows快捷方式,并从那里创建文件和子目录? (或者安装Cygwin并使用符号链接?)
在具有短名称的目录中创建所需文件,压缩/压缩它们,然后将它们解压缩到名称较长的目录中。或者“手动”创建zip / tar文件并将其解压缩到所需位置。