File Coverage

blib/lib/local/c.pm
Criterion Covered Total %
statement 87 182 47.8
branch 22 94 23.4
condition 5 21 23.8
subroutine 23 32 71.8
pod 0 21 0.0
total 137 350 39.1


line stmt bran cond sub pod time code
1 1     1   24591 use strict;
  1         2  
  1         39  
2 1     1   4 use warnings;
  1         2  
  1         57  
3             package local::c;
4             {
5             $local::c::VERSION = '0.001';
6             }
7             # ABSTRACT: Installing C libraries in userspace [HIGHLY EXPERIMENTAL]
8              
9             #
10             # this code is mostly forked of local::lib
11             #
12 1     1   29 use 5.008001;
  1         16  
  1         49  
13              
14 1     1   5 use File::Spec ();
  1         1  
  1         18  
15 1     1   4 use File::Path ();
  1         2  
  1         11  
16 1     1   4 use Carp ();
  1         1  
  1         25  
17 1     1   5 use Config;
  1         2  
  1         2858  
18              
19             our $VERSION ||= '0.000';
20              
21             our @KNOWN_FLAGS = qw(--deactivate --deactivate-all --print-env);
22              
23             our $DEFAULT_PATH = '~/localc';
24              
25             sub DEACTIVATE_ONE () { 1 }
26             sub DEACTIVATE_ALL () { 2 }
27            
28             sub INTERPOLATE_ENV () { 1 }
29             sub LITERAL_ENV () { 0 }
30            
31             sub import {
32 1     1   11 my ($class, @args) = @_;
33            
34 1         2 my %arg_store;
35 1         3 for my $arg (@args) {
36             # check for lethal dash first to stop processing before causing problems
37 0 0       0 if ($arg =~ /−/) {
  0 0       0  
    0          
38 0         0 die <<'DEATH';
39             WHOA THERE! It looks like you've got some fancy dashes in your commandline!
40             These are *not* the traditional -- dashes that software recognizes. You
41             probably got these by copy-pasting from the perldoc for this module as
42             rendered by a UTF8-capable formatter. This most typically happens on an OS X
43             terminal, but can happen elsewhere too. Please try again after replacing the
44             dashes with normal minus signs.
45             DEATH
46             } elsif(grep { $arg eq $_ } @KNOWN_FLAGS) {
47 0         0 (my $flag = $arg) =~ s/--//;
48 0         0 $arg_store{$flag} = 1;
49             } elsif($arg =~ /^--/) {
50 0         0 die "Unknown import argument: $arg";
51             } else {
52             # assume that what's left is a path
53 0         0 $arg_store{path} = $arg;
54             }
55             }
56            
57 1 50       5 my $printenv = defined $arg_store{'print-env'} ? 1 : 0;
58            
59 1         1 my $deactivating = 0;
60 1 50       4 if ($arg_store{deactivate}) {
61 0         0 $deactivating = DEACTIVATE_ONE;
62             }
63 1 50       4 if ($arg_store{'deactivate-all'}) {
64 0         0 $deactivating = DEACTIVATE_ALL;
65             }
66            
67 1         5 $arg_store{path} = $class->resolve_path($arg_store{path});
68 1         17 $class->setup_local_c_for($arg_store{path}, $deactivating, $printenv);
69            
70             }
71            
72             sub pipeline;
73            
74             sub pipeline {
75 3     3 0 7 my @methods = @_;
76 3         13 my $last = pop(@methods);
77 3 100       7 if (@methods) {
78             \sub {
79 2     2   4 my ($obj, @args) = @_;
80 2         8 $obj->${pipeline @methods}(
  2         7  
81             $obj->$last(@args)
82             );
83 2         15 };
84             } else {
85             \sub {
86 1     1   8 shift->$last(@_);
87 1         10 };
88             }
89             }
90            
91            
92             sub _uniq {
93 0     0   0 my %seen;
94 0         0 grep { ! $seen{$_}++ } @_;
  0         0  
95             }
96            
97             sub resolve_path {
98 1     1 0 4 my ($class, $path) = @_;
99 1         2 $class->${pipeline qw(
  1         7  
100             resolve_relative_path
101             resolve_home_path
102             resolve_empty_path
103             )}($path);
104             }
105            
106             sub resolve_empty_path {
107 1     1 0 1 my ($class, $path) = @_;
108 1 50       3 if (defined $path) {
109 0         0 $path;
110             } else {
111 1         2 $DEFAULT_PATH;
112             }
113             }
114            
115            
116             sub resolve_home_path {
117 1     1 0 1 my ($class, $path) = @_;
118 1 50       8 return $path unless ($path =~ /^~/);
119 1         6 my ($user) = ($path =~ /^~([^\/]+)/); # can assume ^~ so undef for 'us'
120 1         1 my $tried_file_homedir;
121 1         2 my $homedir = do {
122 1 50 33     2 if (eval { require File::HomeDir } && $File::HomeDir::VERSION >= 0.65) {
  1         2152  
123 1         9859 $tried_file_homedir = 1;
124 1 50       4 if (defined $user) {
125 0         0 File::HomeDir->users_home($user);
126             } else {
127 1         8 File::HomeDir->my_home;
128             }
129             } else {
130 0 0       0 if (defined $user) {
131 0         0 (getpwnam $user)[7];
132             } else {
133 0 0       0 if (defined $ENV{HOME}) {
134 0         0 $ENV{HOME};
135             } else {
136 0         0 (getpwuid $<)[7];
137             }
138             }
139             }
140             };
141 1 50       59 unless (defined $homedir) {
142 0 0       0 Carp::croak(
    0          
143             "Couldn't resolve homedir for "
144             .(defined $user ? $user : 'current user')
145             .($tried_file_homedir ? '' : ' - consider installing File::HomeDir')
146             );
147             }
148 1         8 $path =~ s/^~[^\/]*/$homedir/;
149 1         4 $path;
150             }
151            
152             sub resolve_relative_path {
153 1     1 0 3 my ($class, $path) = @_;
154 1         42 $path = File::Spec->rel2abs($path);
155             }
156            
157             sub setup_local_c_for {
158 1     1 0 3 my ($class, $path, $deactivating, $printenv) = @_;
159            
160 1         3 my $interpolate = LITERAL_ENV;
161 1         5 my @active_lcs = $class->active_paths;
162            
163 1         5 $path = $class->ensure_dir_structure_for($path);
164            
165 1 50       4 if (! $deactivating) {
166 1 50 33     8 if (@active_lcs && $active_lcs[-1] eq $path) {
  0 50       0  
167 0 0       0 exit 0 if $0 eq '-';
168 0         0 return; # Asked to add what's already at the top of the stack
169             } elsif (grep { $_ eq $path} @active_lcs) {
170             # Asked to add a dir that's lower in the stack -- so we remove it from
171             # where it is, and then add it back at the top.
172 0         0 $class->setup_env_hash_for($path, DEACTIVATE_ONE);
173             # Which means we can no longer output "PERL5LIB=...:$PERL5LIB" stuff
174             # anymore because we're taking something *out*.
175 0         0 $interpolate = INTERPOLATE_ENV;
176             }
177             }
178            
179 1 50 33     8 if ($0 eq '-' or $printenv) {
180 0         0 $class->print_environment_vars_for($path, $deactivating, $interpolate);
181 0         0 exit 0;
182             } else {
183 1         4 $class->setup_env_hash_for($path, $deactivating);
184             }
185             }
186            
187             sub install_base_bin_path {
188 1     1 0 2 my ($class, $path) = @_;
189 1         14 File::Spec->catdir($path, 'bin');
190             }
191              
192             sub install_base_pkg_config_path {
193 1     1 0 2 my ($class, $path) = @_;
194 1         24 File::Spec->catdir($path, 'lib', 'pkgconfig');
195             }
196            
197             sub ensure_dir_structure_for {
198 1     1 0 2 my ($class, $path) = @_;
199 1 50       22 unless (-d $path) {
200 0         0 warn "Attempting to create directory ${path}\n";
201             }
202 1         53 File::Path::mkpath($path);
203             # Need to have the path exist to make a short name for it, so
204             # converting to a short name here.
205 1 50       5 $path = Win32::GetShortPathName($path) if $^O eq 'MSWin32';
206            
207 1         2 return $path;
208             }
209            
210             sub guess_shelltype {
211 0     0 0 0 my $shellbin = 'sh';
212 0 0       0 if(defined $ENV{'SHELL'}) {
213 0         0 my @shell_bin_path_parts = File::Spec->splitpath($ENV{'SHELL'});
214 0         0 $shellbin = $shell_bin_path_parts[-1];
215             }
216 0         0 my $shelltype = do {
217 0         0 local $_ = $shellbin;
218 0 0       0 if(/csh/) {
219 0         0 'csh'
220             } else {
221 0         0 'bourne'
222             }
223             };
224            
225             # Both Win32 and Cygwin have $ENV{COMSPEC} set.
226 0 0 0     0 if (defined $ENV{'COMSPEC'} && $^O ne 'cygwin') {
227 0         0 my @shell_bin_path_parts = File::Spec->splitpath($ENV{'COMSPEC'});
228 0         0 $shellbin = $shell_bin_path_parts[-1];
229 0         0 $shelltype = do {
230 0         0 local $_ = $shellbin;
231 0 0       0 if(/command\.com/) {
    0          
    0          
232 0         0 'win32'
233             } elsif(/cmd\.exe/) {
234 0         0 'win32'
235             } elsif(/4nt\.exe/) {
236 0         0 'win32'
237             } else {
238 0         0 $shelltype
239             }
240             };
241             }
242 0         0 return $shelltype;
243             }
244            
245             sub print_environment_vars_for {
246 0     0 0 0 my ($class, $path, $deactivating, $interpolate) = @_;
247 0         0 print $class->environment_vars_string_for($path, $deactivating, $interpolate);
248             }
249            
250             sub environment_vars_string_for {
251 0     0 0 0 my ($class, $path, $deactivating, $interpolate) = @_;
252 0         0 my @envs = $class->build_environment_vars_for($path, $deactivating, $interpolate);
253 0         0 my $out = '';
254            
255             # rather basic csh detection, goes on the assumption that something won't
256             # call itself csh unless it really is. also, default to bourne in the
257             # pathological situation where a user doesn't have $ENV{SHELL} defined.
258             # note also that shells with funny names, like zoid, are assumed to be
259             # bourne.
260            
261 0         0 my $shelltype = $class->guess_shelltype;
262            
263 0         0 while (@envs) {
264 0         0 my ($name, $value) = (shift(@envs), shift(@envs));
265 0 0       0 $value =~ s/(\\")/\\$1/g if defined $value;
266 0         0 $out .= $class->${\"build_${shelltype}_env_declaration"}($name, $value);
  0         0  
267             }
268 0         0 return $out;
269             }
270            
271             # simple routines that take two arguments: an %ENV key and a value. return
272             # strings that are suitable for passing directly to the relevant shell to set
273             # said key to said value.
274             sub build_bourne_env_declaration {
275 0     0 0 0 my $class = shift;
276 0         0 my($name, $value) = @_;
277 0 0       0 return defined($value) ? qq{export ${name}="${value}";\n} : qq{unset ${name};\n};
278             }
279            
280             sub build_csh_env_declaration {
281 0     0 0 0 my $class = shift;
282 0         0 my($name, $value) = @_;
283 0 0       0 return defined($value) ? qq{setenv ${name} "${value}"\n} : qq{unsetenv ${name}\n};
284             }
285            
286             sub build_win32_env_declaration {
287 0     0 0 0 my $class = shift;
288 0         0 my($name, $value) = @_;
289 0 0       0 return defined($value) ? qq{set ${name}=${value}\n} : qq{set ${name}=\n};
290             }
291            
292             sub setup_env_hash_for {
293 1     1 0 2 my ($class, $path, $deactivating) = @_;
294 1         6 my %envs = $class->build_environment_vars_for($path, $deactivating, INTERPOLATE_ENV);
295 1         31 @ENV{keys %envs} = values %envs;
296             }
297            
298             sub build_environment_vars_for {
299 1     1 0 2 my ($class, $path, $deactivating, $interpolate) = @_;
300            
301 1 50       6 if ($deactivating == DEACTIVATE_ONE) {
    50          
302 0         0 return $class->build_deactivate_environment_vars_for($path, $interpolate);
303             } elsif ($deactivating == DEACTIVATE_ALL) {
304 0         0 return $class->build_deact_all_environment_vars_for($path, $interpolate);
305             } else {
306 1         5 return $class->build_activate_environment_vars_for($path, $interpolate);
307             }
308             }
309            
310             sub build_activate_environment_vars_for {
311 1     1 0 3 my ($class, $path, $interpolate) = @_;
312             return (
313 1 0 0     21 LOCAL_C_PREFIX => $path,
    0 33        
    50 33        
    0          
    0          
    50          
    0          
    50          
314             LOCAL_C_PREFIXES =>
315             join($Config{path_sep},
316             ( $ENV{LOCAL_C_PREFIXES}
317             ? ( $interpolate == INTERPOLATE_ENV
318             ? ( $ENV{LOCAL_C_PREFIXES} || () )
319             : ( ($^O ne 'MSWin32') ? '$LOCAL_C_PREFIXES' : '%LOCAL_C_PREFIXES%' )
320             )
321             : ()
322             ),
323             $path
324             ),
325             PKG_CONFIG_PATH =>
326             join($Config{path_sep},
327             $class->install_base_pkg_config_path($path),
328             ( $interpolate == INTERPOLATE_ENV
329             ? ( $ENV{PKG_CONFIG_PATH} || () )
330             : ( $ENV{PKG_CONFIG_PATH}
331             ? ( ($^O ne 'MSWin32') ? '$PKG_CONFIG_PATH' : '%PKG_CONFIG_PATH%' )
332             : ()
333             )
334             )
335             ),
336             PATH =>
337             join($Config{path_sep},
338             $class->install_base_bin_path($path),
339             ( $interpolate == INTERPOLATE_ENV
340             ? ( $ENV{PATH} || () )
341             : ( ($^O ne 'MSWin32') ? '$PATH' : '%PATH%' )
342             )
343             ),
344             );
345             }
346            
347             sub active_paths {
348 1     1 0 3 my ($class) = @_;
349            
350 1 50       7 return () unless defined $ENV{LOCAL_C_PREFIXES};
351 0           return split /\Q$Config{path_sep}/, $ENV{LOCAL_C_PREFIXES};
352             }
353            
354             sub build_deactivate_environment_vars_for {
355 0     0 0   my ($class, $path, $interpolate) = @_;
356            
357 0           my @active_lcs = $class->active_paths;
358            
359 0 0         if (!grep { $_ eq $path } @active_lcs) {
  0            
360 0           warn "Tried to deactivate inactive local::c '$path'\n";
361 0           return ();
362             }
363            
364 0           my @new_lc_root = grep { $_ ne $path } @active_lcs;
  0            
365            
366 0           my %env = (
367             LOCAL_C_PREFIX => (@active_lcs ? $active_lcs[0] : undef),
368             LOCAL_C_PREFIXES => (@new_lc_root ?
369             join($Config{path_sep}, @new_lc_root) : undef
370             ),
371             PATH => join($Config{path_sep},
372 0           grep { $_ ne $class->install_base_bin_path($path) }
373             split /\Q$Config{path_sep}/, $ENV{PATH}
374             ),
375             PKG_CONFIG_PATH => join($Config{path_sep},
376 0 0         grep { $_ ne $class->install_base_pkg_config_path($path) }
    0          
377             split /\Q$Config{path_sep}/, $ENV{PKG_CONFIG_PATH}
378             ),
379             );
380            
381 0           return %env;
382             }
383            
384             sub build_deact_all_environment_vars_for {
385 0     0 0   my ($class, $path, $interpolate) = @_;
386            
387 0           my @active_lcs = $class->active_paths;
388            
389 0           my @new_path = split /\Q$Config{path_sep}/, $ENV{PATH};
390 0           my @new_pkg_config = split /\Q$Config{path_sep}/, $ENV{PKG_CONFIG_PATH};
391            
392 0           for my $path (@active_lcs) {
393 0           @new_path = grep {
394 0           $_ ne $class->install_base_bin_path($path)
395             } @new_path;
396 0           @new_pkg_config = grep {
397 0           $_ ne $class->install_base_pkg_config_path($path)
398             } @new_pkg_config;
399             }
400            
401 0           my %env = (
402             LOCAL_C_PREFIXES => undef,
403             PATH => join($Config{path_sep}, @new_path),
404             PKG_CONFIG_PATH => join($Config{path_sep}, @new_pkg_config),
405             );
406            
407 0           return %env;
408             }
409              
410             1;
411              
412              
413             __END__