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 )