19

我有一个构建过程的一部分,它在 Windows 中创建了一个非常长的路径。这不是我的错。有好几个目录,而且没有一个目录名异常长;它们足够长且数量足以使其结束MAX_PATH(260个字符)。我没有在这些名称中使用除 ASCII 以外的任何东西。

最大的问题是,在目标期间, Module::Build的内部深处发生了爆炸dist,尽管我认为构建系统并不重要,因为它们会创建相同的目录。

创建这些过长目录之一File::Path失败:

 use File::Path qw( make_path );

 make_path( 'C:\\.....' ); # fails if path is over 260 chars

同样,一旦绝对路径超过,手动构建每个目录级别都会失败MAX_PATH

这不是新的,也不是 Perl 的错,微软在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.

我向 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注意到一些 Unicode 文件名问题Win32API::File,但没有特别提到长路径。但是,Win32API::File CPAN 论坛条目似乎只表示恐惧,这会导致愤怒,这会导致仇恨,等等。Perlmonks帖子中有一个示例How to stat a file with a Unicode (UTF16-LE) filename in Windows? . 似乎这Win32::CreateDirectory就是答案,下次我在 Windows 机器旁边时会尝试这样做。

然后,假设我可以创建长路径路径。现在我必须教 Module::Build,也许还有其他东西来处理它。Win32::GetANSIPathName()如果按照锡上所说的那样使用monkeypatches,这可能会立即变得容易。

4

5 回答 5

10

对于需要处理字符串的每个函数,Windows 有两个单独的系统调用,一个使用 ANSI aka Active Code Page 作为编码的“A”调用(例如 cp1252)和一个使用 UTF-16le 的“W”调用。Perl 使用“A”调用,而\\?\仅适用于“W”调用。

您可以使用 Win32::API 访问“W”调用,如下面的脚本所示,但Win32::LongPath不仅使用“W”调用,而且会自动添加\\?\!

使用 Win32::API 调用CreateDirectoryW以使用长路径(\\?\-前缀路径)的示例:

#!/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;
}
于 2009-11-12T13:37:34.243 回答
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 );
于 2009-11-12T15:09:52.000 回答
2

我知道这不是您特定问题的解决方案。但是,在很多情况下,能够将很长的路径映射到驱动器号可以让人们回避这个问题,因此在处理很长的路径名时很有用,而不必费力地处理很多Windows 特定代码和文档。

尽管我付出了所有努力来弄清楚如何做到这一点,但我还是建议以某种方式使用SUBST. Win32::FileOp提供SubstUnsubst. 然后,您可以将顶级工作目录映射到未使用的驱动器号(您可以使用 找到Substed)。我会开始检查Z并向后工作。

或者,您可以退出,调用subst不带参数的实用程序来获取当前替换列表,选​​择一个不存在的替换。

这些都不是完全安全的,因为替换可能会在构建过程中发生变化。

于 2009-11-12T15:39:37.757 回答
2

这确实应该是一条评论,但在评论中发布代码几乎没有用。

UNC 路径也不起作用:

C:\> 净份额
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 \\计算机\perlbuild\0123456789\0123456789\0123456789\0123456789\0123456
789\0123456789\0123456789\0123456789\0123456789\0123456789\0123456789\0123456789
\0123456789\0123456789\0123456789\0123456789\0123456789\0123456789\0123456789\01
23456789\0123456789:没有这样的文件或目录;文件名或扩展名太
 长在 C:\Temp\k.pl 第 15 行
于 2009-11-13T16:49:28.130 回答
-1

我有三个想法,都是些小技巧:

  1. 从一些短目录名开始(C:\data_directory\a\b\c\d\4\5\6\...),然后重命名目录(当然首先从最深的目录开始)。

  2. 为中等长度的路径创建 Windows 快捷方式并从那里创建文件和子目录?(或者安装 Cygwin 并使用符号链接?)

  3. 在具有短名称的目录中创建所需的文件,对它们进行 zip/tar,然后将它们解压缩到具有较长名称的目录中。或者“手动”创建 zip/tar 文件并将它们解压缩到所需位置。

于 2009-11-12T14:46:22.043 回答