Alien-Xmake

 view release on metacpan or  search on metacpan

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

        my @args;
        push @args, '-f'     if $opts{force};
        push @args, '--list' if $opts{list};
        push @args, '-l', $opts{language} if $opts{language};
        push @args, '-t', $opts{template} if $opts{template};
        push @args, '-P', $opts{project}  if $opts{project};
        push @args, $target if defined $target && length $target;
        push @args, $self->_extra_args( \%opts );
        $self->_run( 'create', @args );
    }

    method configure (%opts) {
        my @args = $self->_config_args( \%opts );
        push @args, '-c'                        if $opts{clean};
        push @args, '--check'                   if $opts{check};
        push @args, '--menu'                    if $opts{menu};
        push @args, '--export=' . $opts{export} if $opts{export};
        push @args, '--import=' . $opts{import} if $opts{import};
        push @args, '-o', $opts{builddir} if $opts{builddir};
        push @args, $self->_extra_args( \%opts );
        return 1 if $self->_run( 'config', @args );

        # The first attempt found no usable toolchain (e.g. bare Strawberry Perl
        # on Windows, where xmake's windows platform has no gcc/cc toolchain).
        # Retry once, pinning whatever C compiler is reachable on PATH, unless
        # the caller already asked for a specific one.
        return 0 if $opts{toolchain};
        my $compiler = _c_compiler_on_path();
        return 0 unless $compiler;
        $self->_hint("config (-m $opts{mode}) failed; retrying with --toolchain=$compiler");
        $self->_run( 'config', @args, '--toolchain=' . $compiler );
    }

    # The first config attempt found no usable toolchain; say so and retry.
    method _hint ($msg) { print STDERR "Alien::Xmake: $msg\n" if $verbose }

    # First C compiler actually present on PATH; returns the xmake toolchain name.
    sub _c_compiler_on_path () {
        state $tc = do {
            my $windows = $^O eq 'MSWin32';
            my $sep     = $windows ? ';' : ':';
            my @dirs    = split /$sep/, ( $ENV{PATH} // '' );
            my @wanted  = ( [ 'gcc' => qw[gcc cc] ], [ 'clang' => 'clang' ], [ 'clang-cl' => 'clang-cl' ] );
            my $hit;
            for my $dir (@dirs) {
                for my $ext ( '', $windows ? ( '.exe', '.bat', '.cmd' ) : () ) {
                    for my $pair (@wanted) {
                        my ( $toolchain, @bins ) = @$pair;
                        next unless grep { -f File::Spec->catfile( $dir, $_ . $ext ) } @bins;
                        $hit = $toolchain;
                        last;
                    }
                    last if $hit;
                }
                last if $hit;
            }
            $hit;
        };
    }

    method global (%opts) {
        my @args;
        push @args, '-c'                                                             if $opts{clean};
        push @args, '--check'                                                        if $opts{check};
        push @args, '--menu'                                                         if $opts{menu};
        push @args, '--theme=' . $opts{theme}                                        if $opts{theme};
        push @args, '--debugger=' . $opts{debugger}                                  if $opts{debugger};
        push @args, '--ccache=' . $opts{ccache}                                      if defined $opts{ccache};
        push @args, '--cachedir=' . $opts{cachedir}                                  if $opts{cachedir};
        push @args, '--policies=' . $opts{policies}                                  if $opts{policies};
        push @args, '--network=' . $opts{network}                                    if $opts{network};
        push @args, '--insecure-ssl'                                                 if $opts{insecure_ssl};
        push @args, '--proxy=' . $opts{proxy}                                        if $opts{proxy};
        push @args, '--proxy_hosts=' . $opts{proxy_hosts}                            if $opts{proxy_hosts};
        push @args, '--proxy_pac=' . $opts{proxy_pac}                                if $opts{proxy_pac};
        push @args, '--pkg_searchdirs=' . $self->_join( $opts{pkg_searchdirs}, ';' ) if $opts{pkg_searchdirs};
        push @args, '--pkg_cachedir=' . $opts{pkg_cachedir}                          if $opts{pkg_cachedir};
        push @args, '--pkg_installdir=' . $opts{pkg_installdir}                      if $opts{pkg_installdir};
        push @args, $self->_global_config_args( \%opts );
        push @args, $self->_extra_args( \%opts );
        $self->_run( 'global', @args );
    }

    method install ( $target //= (), %opts ) {
        my @args;
        push @args, '-o', $opts{installdir} if $opts{installdir};
        push @args, '--bindir=' . $opts{bindir}         if $opts{bindir};
        push @args, '--libdir=' . $opts{libdir}         if $opts{libdir};
        push @args, '--includedir=' . $opts{includedir} if $opts{includedir};
        push @args, '-g', $opts{group} if $opts{group};
        push @args, '-a'                              if $opts{all};
        push @args, '--binaries=' . $opts{binaries}   if defined $opts{binaries};
        push @args, '--headers=' . $opts{headers}     if defined $opts{headers};
        push @args, '--libraries=' . $opts{libraries} if defined $opts{libraries};
        push @args, '--packages=' . $opts{packages}   if defined $opts{packages};
        push @args, '--admin'                         if $opts{admin};
        push @args, $target                           if defined $target && length $target;
        push @args, $self->_extra_args( \%opts );
        $self->_run( 'install', @args );
    }

    method uninstall ( $target //= (), %opts ) {
        my @args;
        push @args, '--installdir=' . $opts{installdir} if $opts{installdir};
        push @args, '--bindir=' . $opts{bindir}         if $opts{bindir};
        push @args, '--libdir=' . $opts{libdir}         if $opts{libdir};
        push @args, '--includedir=' . $opts{includedir} if $opts{includedir};
        push @args, '-g', $opts{group} if $opts{group};
        push @args, '--admin' if $opts{admin};
        push @args, $target   if defined $target && length $target;
        push @args, $self->_extra_args( \%opts );
        $self->_run( 'uninstall', @args );
    }

    method package ( $target //= (), %opts ) {
        my @args;
        push @args, '-o', $opts{outputdir} if $opts{outputdir};
        push @args, '-a' if $opts{all};
        push @args, '-f', $opts{format} if $opts{format};
        push @args, '--homepage=' . $opts{homepage}       if $opts{homepage};
        push @args, '--description=' . $opts{description} if $opts{description};
        push @args, '--url=' . $opts{url}                 if $opts{url};
        push @args, '--version=' . $opts{version}         if defined $opts{version};
        push @args, '--shasum=' . $opts{shasum}           if $opts{shasum};
        push @args, $target                               if defined $target && length $target;
        push @args, $self->_extra_args( \%opts );
        $self->_run( 'package', @args );
    }

    method pack ( $package //= (), %opts ) {
        my @args;
        push @args, '-o', $opts{outputdir} if $opts{outputdir};
        push @args, '--basename=' . $opts{basename}   if $opts{basename};
        push @args, '--autobuild=' . $opts{autobuild} if defined $opts{autobuild};
        push @args, '-j', $opts{jobs}                    if $opts{jobs};
        push @args, '-f', $self->_join( $opts{formats} ) if $opts{formats};
        push @args, $package if defined $package && length $package;
        push @args, $self->_extra_args( \%opts );
        $self->_run( 'pack', @args );
    }

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

        push @args, '-l' if $opts{list};
        push @args, '-d', $opts{deserialize} if $opts{deserialize};
        if ( defined $script && length $script ) {
            if ( $opts{command} || $opts{stdin} || !-f $script ) {
                push @args, $self->_lua_script_file($script);
            }
            else {
                push @args, $script;    # an existing script file on disk
            }
        }
        push @args, $self->_extra_args( \%opts );
        return $self->_out( 'lua', \%opts, @args ) if $opts{list};
        $self->_run( 'lua', @args );
    }

    # Write inline Lua to a temporary .lua file and return its path. The file lives in a temp dir
    # created per call and is removed once the argument list is no longer needed.
    method _lua_script_file ($code) {
        my $dir  = tempdir( 'alien-xmake-lua-XXXXXX', CLEANUP => 1 );
        my $file = File::Spec->catfile( $dir, 'script.lua' );
        open my $fh, '>', $file or die "Alien::Xmake: cannot write $file: $!";
        print {$fh} $code;
        close $fh or die "Alien::Xmake: cannot close $file: $!";
        return $file;
    }

    method macro ( $name //= (), %opts ) {
        my @args;
        push @args, '-b'                        if $opts{begin};
        push @args, '-e'                        if $opts{end};
        push @args, '--show'                    if $opts{show};
        push @args, '-l'                        if $opts{list};
        push @args, '-d'                        if $opts{delete};
        push @args, '-c'                        if $opts{clear};
        push @args, '--import=' . $opts{import} if $opts{import};
        push @args, '--export=' . $opts{export} if $opts{export};
        push @args, $name                       if defined $name && length $name;
        push @args, $self->_extra_args( \%opts );
        return $self->_out( 'macro', \%opts, @args ) if $opts{list} || $opts{show};
        $self->_run( 'macro', @args );
    }

    method project (%opts) {
        my @args;
        push @args, '-k', $opts{kind}                  if $opts{kind};
        push @args, '-m', $self->_join( $opts{modes} ) if $opts{modes};
        push @args, '-a', $self->_join( $opts{archs} ) if $opts{archs};
        push @args, '-t', $opts{target}                if $opts{target};
        push @args, '--lsp=' . $opts{lsp} if $opts{lsp};
        push @args, $self->_extra_args( \%opts );
        $self->_run( 'project', @args );
    }

    method repo ( $name //= (), %opts ) {
        my @args;
        push @args, '-a'          if $opts{add};
        push @args, '-r'          if $opts{remove};
        push @args, '-u'          if $opts{update};
        push @args, '-c'          if $opts{clear};
        push @args, '-l'          if $opts{list};
        push @args, '-g'          if $opts{global};
        push @args, $name         if defined $name && length $name;
        push @args, $opts{url}    if $opts{url};
        push @args, $opts{branch} if $opts{branch};
        push @args, $self->_extra_args( \%opts );
        return $self->_out( 'repo', \%opts, @args ) if $opts{list};
        $self->_run( 'repo', @args );
    }

    method show ( $list //= (), %opts ) {
        my @args;
        push @args, '-l', $list        if defined $list && length $list;
        push @args, '-g', $opts{group} if $opts{group};
        push @args, '--pretty'                  if $opts{pretty};
        push @args, '--format=' . $opts{format} if $opts{format};
        push @args, '--target=' . $opts{target} if $opts{target};
        push @args, '--info=' . $opts{info}     if $opts{info};
        push @args, $self->_extra_args( \%opts );
        my ( $out, $err, $exit ) = $self->_capture( 'show', @args );
        return () if $exit != 0;
        $out =~ s/\e\[[0-9;]*m//g;
        $out =~ s/\[0m//g;
        return decode_json($out) if ( $opts{format} // '' ) eq 'json' && $out =~ /[\{\[]/;
        return grep {/\S/} split( /\s+/, $out ) if defined $list && length $list;
        return split /\n/, $out;
    }

    # xmake show -t <target> rich target info (JSON by default; pass format/pretty/plain to tune).
    method target_info ( $name, %opts ) {
        my $plain = $opts{plain} || ( ( $opts{format} // '' ) eq 'plain' );
        my @args;
        push @args, '-t', $name if defined $name && length $name;
        push @args, '--format=json' unless $plain;
        push @args, '--pretty'                if $opts{pretty} && $plain;
        push @args, '--group=' . $opts{group} if $opts{group};
        push @args, $self->_extra_args( \%opts );
        my ( $out, $err, $exit ) = $self->_capture( 'show', @args );
        return () if $exit != 0;
        $out =~ s/\e\[[0-9;]*m//g;
        return $out if $plain;
        my $data = eval { decode_json($out) };
        return ()         if !$data;
        return $data      if ref $data eq 'HASH';
        return $data->[0] if ref $data eq 'ARRAY' && ref( $data->[0] ) eq 'HASH';
        return $data;
    }

    # Whatever Lua the user runs, xmake's lua runner always appends a stray, empty "{ }" chunk
    # (even on success), so we can't blindly decode_json the whole line. Strip that artifact plus
    # surrounding whitespace, then decode whatever single JSON value remains.
    method _first_json ($out) {
        $out =~ s/\e\[[0-9;]*m//g;
        $out =~ s/\s*\{\s*\}\s*$//;    # drop xmake's trailing empty-object artifact
        $out =~ s/^\s+//;
        $out =~ s/\s+$//;
        return undef unless length $out;
        my $json = eval { decode_json($out) };
        return undef if !$json;
        return $json;
    }

    # Run inline Lua that emits (or returns) one JSON value and return the decoded structure.
    method lua_json ( $code //= '', %opts ) {
        my $src = length( $opts{code} // '' ) ? $opts{code} : $code;
        if ( $opts{return} ) {
            $src =~ s/^\s*return\b//;    # allow "return <expr>" as well as a bare <expr>
            $src = qq{local j = import("core.base.json")\nlocal v = ($src)\nprint(j.encode(v))};
        }
        my $file = $self->_lua_script_file($src);
        my ( $out, $err, $exit ) = $self->_capture( 'lua', $file );
        my $data = $self->_first_json($out);
        return () if !defined $data;
        return $data;
    }

    method watch (%opts) {
        my @args;
        push @args, '-c', $opts{commands}                       if $opts{commands};
        push @args, '-s', $opts{script}                         if $opts{script};
        push @args, '-d', $self->_join( $opts{watchdirs}, ';' ) if $opts{watchdirs};
        push @args, '-p', $self->_join( $opts{plaindirs}, ';' ) if $opts{plaindirs};
        push @args, '-r' if $opts{run};
        push @args, '-t', $opts{target} if $opts{target};
        push @args, '--' if $opts{argv};
        push @args, @{ $opts{argv} // [] };
        push @args, $self->_extra_args( \%opts );
        $self->_run( 'watch', @args );
    }

    # Run a task and return its captured output (string or decoded JSON for --format=json).
    method _out ( $action, $opts, @args ) {
        my ( $out, $err, $exit ) = $self->_capture( $action, @args );
        return ()                if $exit != 0;
        return decode_json($out) if ( $opts->{format} // '' ) eq 'json' && $out =~ /[\{\[]/;
        $out;
    }

    # config-style keys that `xmake global` actually accepts.
    method _global_config_args ($opts) {
        my @args;
        for my $key (qw[android_sdk build_toolver cuda emsdk mingw ndk ndk_sdkver qt qt_host vcpkg vs wdk]) {
            push @args, "--$key=" . $self->_join( $opts->{$key}, ';' ) if defined $opts->{$key};
        }
        push @args, "--$_=$opts->{set}{$_}" for sort keys %{ $opts->{set} // {} };
        @args;
    }

    # Translate the shared xmake `f`-style option names into command line switches.
    method _config_args ($opts) {
        my @args;
        push @args, '-p', $opts->{plat} if $opts->{plat};
        push @args, '-a', $opts->{arch} if $opts->{arch};
        push @args, '-m', $opts->{mode} if $opts->{mode};
        push @args, '-k', $opts->{kind} if $opts->{kind};
        for my $key (
            qw[toolchain toolchain_host cross target_os bin sdk runtimes ndk ndk_sdkver android_sdk
            build_toolver ndk_stdcxx cuda cuda_sdkver qt qt_host qt_sdkver vcpkg mingw emsdk vs vs_toolset
            vs_sdkver vs_runtime wdk wdk_sdkver wdk_winver debugger ccache ccachedir trybuild tryconfigs
            require pkg_searchdirs pkg_cachedir pkg_installdir rc rcld rcar rcsh fc fcld fcsh linkdirs links
            syslinks includedirs ]
        ) {
            push @args, "--$key=" . $self->_join( $opts->{$key}, ';' ) if defined $opts->{$key};
        }
        push @args, "--$_=$opts->{set}{$_}" for sort keys %{ $opts->{set} // {} };
        @args;
    }

    # Introspection helpers
    method _getver() {
        my ( $ver, undef ) = $self->_getver_build;
        "v$ver";
    }

    method _getbuild() {
        my ( undef, $build ) = $self->_getver_build;
        $build;
    }

    method _getver_build() {
        my $cmd = $self->exe;
        state $out //= do {
            my ( $stdout, undef, $exit ) = capture_cmd( $cmd, '--version' );
            $exit == 0 ? $stdout : '';
        };
        return ( $1, $2 ) if $out =~ /xmake\s+v?(\d+\.\d+\.\d+)(?:\+(.+),)?/i;
        ( '0.0.0', () );
    }

    method _scrub_env () {
        delete @ENV{qw[XMAKE_PROGRAM_DIR XMAKE_PROGRAM_FILE XMAKE_ROOTDIR]} if $self->install_type eq 'share';
    }

    # Resolve absolute path without quotes
    method _resolve_path () {
        my $bin = $config->{bin};

        # If ConfigData failed or we are in a fallback state:
        $bin = File::Spec->catfile( $dir, 'xmake' . ( $windows ? '.exe' : '' ) ) if !$bin && $dir;
        $bin //= 'xmake';



( run in 0.826 second using v1.01-cache-2.11-cpan-1236ad508bc )