Alien-Build

 view release on metacpan or  search on metacpan

lib/Alien/Build.pm  view on Meta::CPAN

              # TODO: this is probably a bug that we don't set
              # download or compelte properties?
              $self->log("single dir, assuming directory");
            }
            else
            {
              $self->log("single file, assuming archive");
            }
            $self->install_prop->{download} = $archive->absolute->stringify;
            $self->install_prop->{complete}->{download} = 1;
            $valid = 1;
          }
          else
          {
            $self->log("multiple files, assuming directory");
            $self->install_prop->{complete}->{download} = 1;
            $self->install_prop->{download} = _path('.')->absolute->stringify;
            $valid = 1;
          }
        },
        after  => sub {
          $CWD = $self->root;
        },
      },
      'download',
    );

    # experimental and undocumented for now
    if($self->meta->has_hook('check_download'))
    {
      $self->meta->call_hook(check_download => $self);
    }

    return $self if $valid;
  }
  else
  {
    # This will call the default download hook
    # defined in Core::Download since the recipe
    # does not provide a download hook
    my $ret = $self->_call_hook('download');

    # experimental and undocumented for now
    if($self->meta->has_hook('check_download'))
    {
      $self->meta->call_hook(check_download => $self);
    }

    return $self;
  }

  die "download failed";
}


sub fetch
{
  my $self = shift;
  my $url = $_[0] || $self->meta_prop->{start_url};

  my $secure = 0;

  if(defined $url && ($url =~ /^(https|file):/ || $url !~ /:/))
  {
    # considered secure when either https or a local file
    $secure = 1;
  }
  elsif(!defined $url)
  {
    $self->log("warning: undefined url in fetch");
  }
  else
  {
    $self->log("warning: attempting to fetch a non-TLS or bundled URL: @{[ $url ]}");
  }

  die "insecure fetch is not allowed" if $self->download_rule =~ /^(encrypt|digest_and_encrypt)$/ && !$secure;

  my $file = $self->_call_hook( 'fetch' => @_ );

  $secure = 0;

  if(ref($file) ne 'HASH')
  {
    $self->log("warning: fetch returned non-hash reference");
  }
  elsif(!defined $file->{protocol})
  {
    $self->log("warning: fetch did not return a protocol");
  }
  elsif($file->{protocol} !~ /^(https|file)$/)
  {
    $self->log("warning: fetch did not use a secure protocol: @{[ $file->{protocol} ]}");
  }
  else
  {
    $secure = 1;
  }

  die "insecure fetch is not allowed" if $self->download_rule =~ /^(encrypt|digest_and_encrypt)$/ && !$secure;

  $file;
}


sub check_digest
{
  my($self, $file) = @_;

  return '' unless $self->meta_prop->{check_digest};

  unless(ref($file) eq 'HASH')
  {
    my $path = Path::Tiny->new($file);
    $file = {
      type     => 'file',
      filename => $path->basename,
      path     => "$path",
      tmp      => 0,
    };
  }

  my $path = $file->{path};
  if(defined $path)
  {
    # there is technically a race condition here
    die "Missing file in digest check: @{[ $file->{filename} ]}" unless -f $path;
    die "Unreadable file in digest check: @{[ $file->{filename} ]}" unless -r $path;
  }
  else
  {
    die "File is wrong type" unless defined $file->{type} && $file->{type} eq 'file';
    die "File has no filename" unless defined $file->{filename};
    die "@{[ $file->{filename} ]} has no content" unless defined $file->{content};
  }

  my $filename = $file->{filename};
  my $signature = $self->meta_prop->{digest}->{$filename} || $self->meta_prop->{digest}->{'*'};

  die "No digest for $filename" unless defined $signature && ref $signature eq 'ARRAY';

  my($algo, $expected) = @$signature;

  if($self->meta->call_hook( check_digest => $self, $file, $algo, $expected ))
  {
    # record the verification here so that we can check in the extract step that the signature
    # was checked.
    $self->install_prop->{download_detail}->{$path}->{digest} = [$algo, $expected] if defined $path; return 1;
  }
  else
  {
    die "No plugin provides digest algorithm for $algo";
  }
}


sub decode
{
  my($self, $res) = @_;
  my $res2 = $self->_call_hook( decode => $res );
  $res2->{protocol} = $res->{protocol}
    if !defined $res2->{protocol}
    &&  defined $res->{protocol};
  return $res2;
}


sub prefer
{
  my($self, $res) = @_;
  my $res2 = $self->_call_hook( prefer => $res );
  $res2->{protocol} = $res->{protocol}
    if !defined $res2->{protocol}
    &&  defined $res->{protocol};
  return $res2;
}


sub extract
{
  my($self, $archive) = @_;

  $archive ||= $self->install_prop->{download};

  unless(defined $archive)
  {
    die "tried to call extract before download";
  }

  {
    my $checked_digest  = 0;
    my $encrypted_fetch = 0;
    my $detail = $self->install_prop->{download_detail}->{$archive};
    if(defined $detail)
    {
      if(defined $detail->{digest})
      {
        my($algo, $expected) = @{ $detail->{digest} };
        my $file = {
          type     => 'file',
          filename => Path::Tiny->new($archive)->basename,
          path     => $archive,
          tmp      => 0,
        };
        $checked_digest = $self->meta->call_hook( check_digest => $self, $file, $algo, $expected )
      }
      if(!defined $detail->{protocol})
      {
        $self->log("warning: extract did not receive protocol details for $archive") unless $checked_digest;
      }
      elsif($detail->{protocol} !~ /^(https|file)$/)
      {
        $self->log("warning: extracting from a file that was fetched via insecure protocol @{[ $detail->{protocol} ]}") unless $checked_digest ;
      }
      else
      {
        $encrypted_fetch = 1;
      }
    }
    else
    {
      $self->log("warning: extract received no download details for $archive");
    }

    if($self->download_rule eq 'digest')
    {
      die "required digest missing for $archive" unless $checked_digest;
    }
    elsif($self->download_rule eq 'encrypt')
    {
      die "file was fetched insecurely for $archive" unless $encrypted_fetch;
    }
    elsif($self->download_rule eq 'digest_or_encrypt')
    {
      die "file was fetched insecurely and required digest missing for $archive" unless $checked_digest || $encrypted_fetch;
    }
    elsif($self->download_rule eq 'digest_and_encrypt')
    {
      die "file was fetched insecurely and required digest missing for $archive" unless $checked_digest || $encrypted_fetch;
      die "required digest missing for $archive" unless $checked_digest;
      die "file was fetched insecurely for $archive" unless $encrypted_fetch;
    }
    elsif($self->download_rule eq 'warn')
    {
      unless($checked_digest || $encrypted_fetch)
      {
        $self->log("!!! NOTICE OF FUTURE CHANGE IN BEHAVIOR !!!");
        $self->log("a future version of Alien::Build will die here by default with this exception: file was fetched insecurely and required digest missing for $archive");
        $self->log("!!! NOTICE OF FUTURE CHANGE IN BEHAVIOR !!!");
      }
    }
    else
    {
      die "internal error, unknown download rule: @{[ $self->download_rule ]}";
    }

  }

  my $nick_name = 'build';

  if($self->meta_prop->{out_of_source})
  {
    $nick_name = 'extract';
    my $extract = $self->install_prop->{extract};
    return $extract if defined $extract && -d $extract;
  }

  my $tmp;
  local $CWD;
  my $ret;

  $self->_call_hook({

    before => sub {
      # called build instead of extract, because this
      # will be used for the build step, and technically
      # extract is a substage of build anyway.
      $tmp = Alien::Build::TempDir->new($self, $nick_name);
      $CWD = "$tmp";
    },
    verify => sub {

      my $path = '.';
      if($self->meta_prop->{out_of_source} && $self->install_prop->{extract})
      {
        $path = $self->install_prop->{extract};
      }

      my @list = grep { $_->basename !~ /^\./ && $_->basename ne 'pax_global_header' } _path($path)->children;

      my $count = scalar @list;

      if($count == 0)
      {
        die "no files extracted";
      }
      elsif($count == 1 && -d $list[0])
      {
        $ret = $list[0]->absolute->stringify;
      }
      else
      {
        $ret = "$tmp";
      }

    },
    after => sub {
      $CWD = $self->root;

lib/Alien/Build.pm  view on Meta::CPAN

 probe [ 'some command %{.meta.plugin_fetch_newprotocol_foo}' ];
 probe [ 'some command %{.install.plugin_fetch_newprotocol_bar}' ];
 probe [ 'some command %{.runtime.plugin_fetch_newprotocol_baz}' ];
 probe [ 'some command %{.meta.my_foo}' ];
 probe [ 'some command %{.install.my_bar}' ];
 probe [ 'some command %{.runtime.my_baz}' ];

=head2 meta_prop

 my $href = $build->meta_prop;
 my $href = Alien::Build->meta_prop;

Meta properties have to do with the recipe itself, and not any particular
instance that probes or builds that recipe.  Meta properties can be changed
from within an L<alienfile> using the C<meta_prop> directive, or from
a plugin from its C<init> method (though should NOT be modified from any
hooks registered within that C<init> method).  This is not strictly enforced,
but if you do not follow this rule your recipe will likely be broken.

=over

=item arch

This is a hint to an installer like L<Alien::Build::MM> or L<Alien::Build::MB>,
that the library or tool contains architecture dependent files and so should
be stored in an architecture dependent location.  If not specified by your
L<alienfile> then it will be set to true.

=item check_digest

True if cryptographic digest should be checked when files are fetched
or downloaded.  This is set by
L<Digest negotiator plugin|Alien::Build::Plugin::Digest::Negotiate>.

=item destdir

Some plugins (L<Alien::Build::Plugin::Build::Autoconf> for example) support
installing via C<DESTDIR>.  They will set this property to true if they
plan on doing such an install.  This helps L<Alien::Build> find the staged
install files and how to locate them.

If available, C<DESTDIR> is used to stage install files in a sub directory before
copying the files into C<blib>.  This is generally preferred method
if available.

=item destdir_filter

Regular expression for the files that should be copied from the C<DESTDIR>
into the stage directory.  If not defined, then all files will be copied.

=item destdir_ffi_filter

Same as C<destdir_filter> except applies to C<build_ffi> instead of C<build>.

=item digest

This properties contains the cryptographic digests (if any) that should
be used when verifying any fetched and downloaded files.  It is a hash
reference where the key is the filename and the value is an array
reference containing a pair of values, the first being the algorithm
('SHA256' is recommended) and the second is the actual digest.  The
special filename C<*> may be specified to indicate that any downloaded
file should match that digest.  If there are both real filenames and
the C<*> placeholder, the real filenames will be used for filenames
that match and any other files will use the placeholder.  Example:

 $build->meta_prop->{digest} = {
   'foo-1.00.tar.gz' => [ SHA256 => '9feac593aa49a44eb837de52513a57736457f1ea70078346c60f0bfc5f24f2c1' ],
   'foo-1.01.tar.gz' => [ SHA256 => '6bbde6a7f10ae5924cf74afc26ff5b7bc4b4f9dfd85c6b534c51bd254697b9e7' ],
   '*'               => [ SHA256 => '33a20aae3df6ecfbe812b48082926d55391be4a57d858d35753ab1334b9fddb3' ],
 };

Cryptographic signatures will only be checked
if the L<check_digest meta property|/check_digest> is set and if the
L<Digest negotiator plugin|Alien::Build::Plugin::Digest::Negotiate> is loaded.
(The Digest negotiator can be used directly, but is also loaded automatically
if you use the L<digest directive|alienfile/digest> is used by the L<alienfile>).

=item env

Environment variables to override during the build stage.

=item env_interpolate

Environment variable values will be interpolated with helpers.  Example:

 meta->prop->{env_interpolate} = 1;
 meta->prop->{env}->{PERL} = '%{perl}';

=item local_source

Set to true if source code package is available locally.  (that is not fetched
over the internet).  This is computed by default based on the C<start_url>
property.  Can be set by an L<alienfile> or plugin.

=item platform

Hash reference.  Contains information about the platform beyond just C<$^O>.

=over 4

=item platform.compiler_type

Refers to the type of flags that the compiler accepts.  May be expanded in the
future, but for now, will be one of:

=over 4

=item microsoft

On Windows when using Microsoft Visual C++

=item unix

Virtually everything else, including gcc on windows.

=back

The main difference is that with Visual C++ C<-LIBPATH> should be used instead
of C<-L>, and static libraries should have the C<.LIB> suffix instead of C<.a>.

lib/Alien/Build.pm  view on Meta::CPAN

plugin instance id that made the probe.  If the probe results in a
system install this will be propagated to C<system_probe_instance_id>
for later use.

=back

=head1 METHODS

=head2 checkpoint

 $build->checkpoint;

Save any install or runtime properties so that they can be reloaded on
a subsequent run in a separate process.  This is useful if your build
needs to be done in multiple stages from a C<Makefile>, such as with
L<ExtUtils::MakeMaker>.  Once checkpointed you can use the C<resume>
constructor (documented above) to resume the probe/build/install]
process.

=head2 root

 my $dir = $build->root;

This is just a shortcut for:

 my $root = $build->install_prop->{root};

Except that it will be created if it does not already exist.

=head2 install_type

 my $type = $build->install_type;

This will return the install type.  (See the like named install property
above for details).  This method will call C<probe> if it has not already
been called.

=head2 is_system_install

 my $boolean = $build->is_system_install;

Returns true if the alien is a system install type.  

=head2 is_share_install

 my $boolean = $build->is_share_install;

Returns true if the alien is a share install type.

=head2 download_rule

 my $rule = $build->download_rule;

This returns install rule as a string.  This is determined by the environment
and should be one of:

=over 4

=item C<warn>

Warn only if fetching via non secure source (secure sources include C<https>,
and bundled files, may include other encrypted protocols in the future).

=item C<digest>

Require that any downloaded source package have a cryptographic signature in
the L<alienfile> and that signature matches what was downloaded.

=item C<encrypt>

Require that any downloaded source package is fetched via secure source.

=item C<digest_or_encrypt>

Require that any downloaded source package is B<either> fetched via a secure source
B<or> has a cryptographic signature in the L<alienfile> and that signature matches
what was downloaded.

=item C<digest_and_encrypt>

Require that any downloaded source package is B<both> fetched via a secure source
B<and> has a cryptographic signature in the L<alienfile> and that signature matches
what was downloaded.

=back

The current default is C<warn>, but in the near future this will be upgraded to
C<digest_or_encrypt>.

=head2 set_prefix

 $build->set_prefix($prefix);

Set the final (unstaged) prefix.  This is normally only called by L<Alien::Build::MM>
and similar modules.  It is not intended for use from plugins or from an L<alienfile>.

=head2 set_stage

 $build->set_stage($dir);

Sets the stage directory.  This is normally only called by L<Alien::Build::MM>
and similar modules.  It is not intended for use from plugins or from an L<alienfile>.

=head2 requires

 my $hash = $build->requires($phase);

Returns a hash reference of the modules required for the given phase.  Phases
include:

=over 4

=item configure

These modules must already be available when the L<alienfile> is read.

=item any

These modules are used during either a C<system> or C<share> install.

=item share

These modules are used during the build phase of a C<share> install.

=item system

These modules are used during the build phase of a C<system> install.

=back

=head2 load_requires

 $build->load_requires($phase);

This loads the appropriate modules for the given phase (see C<requires> above
for a description of the phases).

=head2 probe

 my $install_type = $build->probe;



( run in 0.834 second using v1.01-cache-2.11-cpan-39bf76dae61 )