Skip to content

Commit

Permalink
Merge pull request #14 from moderntribe/fix/parallelize-command-funct…
Browse files Browse the repository at this point in the history
…ions

Allow for pooling multiple commands (npm, composer, etc) to run in parallel
  • Loading branch information
lucatume authored Jun 24, 2020
2 parents eb983d3 + f37f75a commit ff382a4
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 146 deletions.
39 changes: 4 additions & 35 deletions src/commands/composer.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,40 +14,9 @@
$using = tric_target();
echo light_cyan( "Using {$using}\n" );

setup_id();
$composer_command = $args( '...' );
$targets = [ 'target' ];
$command = $args( '...' );
$pool = build_command_pool( 'composer', $command, [ 'common' ] );
$status = execute_command_pool( $pool );

if (
file_exists( tric_plugins_dir( "{$using}/common" ) )
&& ask( "\nWould you also like to run that composer command against common?", 'yes' )
) {
$targets[] = 'common';
}

$command_process = static function( $target ) use ( $using, $composer_command ) {
$prefix = light_cyan( $target );

// Execute composer as the parent.
if ( 'common' === $target ) {
tric_switch_target( "{$using}/common" );
$prefix = yellow( $target );
}

$status = tric_realtime()( array_merge( [ 'run', '--rm', 'composer' ], $composer_command ), $prefix );

if ( 'common' === $target ) {
tric_switch_target( $using );
}

return pcntl_exit( $status );
};

if ( count( $targets ) > 1 ) {
$status = parallel_process( $targets, $command_process );
tric_switch_target( $using );
exit( $status );
}

exit( $command_process( reset( $targets ) ) );
exit( $status );

15 changes: 13 additions & 2 deletions src/commands/init.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,19 @@
setup_plugin_tests( $plugin );

if ( getenv( 'TRIC_BUILD_PROMPT' ) ) {
tric_maybe_run_composer_install( $plugin );
tric_maybe_run_npm_install( $plugin );
$current_target = tric_target();

if ( $current_target !== $plugin ) {
tric_switch_target( $plugin );
}

$command_pool = maybe_build_install_command_pool( 'composer', $plugin, [ 'common' ] );
$command_pool = array_merge( $command_pool, maybe_build_install_command_pool( 'npm', $plugin, [ 'common' ] ) );
execute_command_pool( $command_pool );

if ( $current_target !== $plugin ) {
tric_switch_target( $current_target );
}
}

echo light_cyan( "Finished initializing {$plugin}\n" );
27 changes: 4 additions & 23 deletions src/commands/npm.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,29 +14,10 @@
$using = tric_target();
echo light_cyan( "Using {$using}\n" );

setup_id();
$npm_command = $args( '...' );
$status = tric_realtime()( array_merge( [ 'run', '--rm', 'npm' ], $npm_command ) );
$command = $args( '...' );
$pool = build_command_pool( 'npm', $command, [ 'common' ] );
$status = execute_command_pool( $pool );

// If there is a status other than 0, we have an error. Bail.
if ( $status ) {
exit( $status );
}

if ( ! file_exists( tric_plugins_dir( "{$using}/common" ) ) ) {
return;
}

if ( ask( "\nWould you like to run that npm command against common?", 'yes' ) ) {
tric_switch_target( "{$using}/common" );

echo light_cyan( "Temporarily using " . tric_target() . "\n" );

$status = tric_realtime()( array_merge( [ 'run', '--rm', 'npm' ], $npm_command ) );
exit( $status );

tric_switch_target( $using );

echo light_cyan( "Using " . tric_target() ." once again\n" );
}

exit( $status );
10 changes: 5 additions & 5 deletions src/process.php
Original file line number Diff line number Diff line change
Expand Up @@ -207,20 +207,20 @@ function check_status_or( callable $process, callable $else = null ) {
*
* @return int The combined process status value of all child processes.
*/
function parallel_process( $items, $command_process ) {
function parallel_process( $pool ) {
$process_children = [];

if ( function_exists( 'pcntl_fork' ) ) {
// If we're on a OS that does support process control, then fork.
foreach ( $items as $item ) {
foreach ( $pool as $item ) {
$pid = pcntl_fork();
if ( $pid === - 1 ) {
echo magenta( "Unable to fork processes.\n" );
exit( 1 );
}

if ( 0 === $pid ) {
$command_process( $item );
$item['process']( $item['target'] );
} else {
$process_children[] = $pid;
}
Expand All @@ -233,8 +233,8 @@ function parallel_process( $items, $command_process ) {
* If Process Control functions are not available or are disabled, then we execute the commands serially.
* Nothing "parallel" here.
*/
foreach ( $items as $item ) {
$status = $command_process( $item );
foreach ( $pool as $item ) {
$status = $item['process']( $item['target'] );
if ( $status !== 0 ) {
// At the first failure, bail.
return $status;
Expand Down
155 changes: 74 additions & 81 deletions src/tric.php
Original file line number Diff line number Diff line change
Expand Up @@ -710,122 +710,115 @@ function update_stack_images() {
/**
* Maybe runs composer install on a given target
*
* @param array $base_command Base command to run.
* @param string $target Target to potentially run composer install against.
* @param array $sub_directories Sub directories to prompt for additional execution.
*
* @return null|int Result of command execution.
*/
function tric_maybe_run_build_install( $command, $target ) {
function maybe_build_install_command_pool( $base_command, $target, array $sub_directories = [] ) {
$run = ask(
"\nWould you like to run the {$command} build processes for this plugin?",
"\nWould you like to run the {$base_command} build processes for this plugin?",
'yes'
);

if ( empty( $run ) ) {
return;
}

$current_target = tric_target();

if ( $current_target !== $target ) {
tric_switch_target( $target );
return [];
}

$function = "\Tribe\Test\\tric_run_{$command}_command";
$function( [ 'install' ] );

if ( $current_target !== $target ) {
tric_switch_target( $current_target );
}
return build_command_pool( $base_command, [ 'install' ], $sub_directories );
}

/**
* Maybe runs composer install on a given target
* Run a command using the appropriate service.
*
* @param string $target Target to potentially run composer install against.
*/
function tric_maybe_run_composer_install( $target ) {
return tric_maybe_run_build_install( 'composer', $target );
}

/**
* Maybe runs npm install on a given target
*
* @param string $target Target to potentially run npm install against.
*/
function tric_maybe_run_npm_install( $target ) {
return tric_maybe_run_build_install( 'npm', $target );
}

/**
* Run a command using the `npm` service.
* If any subdirectories are provided and are available in the target, then the user will be prompted to run the same
* command on those subdirectories.
*
* If `common` is available in the target and the command dos not fail, then the user will be prompted to run the same
* command on `common`.
* @param string $base_command The base service command to run, e.g. `npm`, `composer`, etc.
* @param array<string> $command The command to run, e.g. `['install','--save-dev']` in array format.
* @param array<string> $sub_directories Sub directories to prompt for additional execution.
*
* @param array<string> $command The `npm` command to run, e.g. `['install','--save-dev']` in array format.
* @return int Result of command execution.
*/
function tric_run_npm_command( array $command ) {
$using = tric_target();
echo light_cyan( "Using {$using}\n" );

setup_id();
$status = tric_realtime()( array_merge( [ 'run', '--rm', 'npm' ], $command ) );
function build_command_pool( string $base_command, array $command, array $sub_directories = [] ) {
$using = tric_target();
$targets = [ 'target' ];

if ( 0 !== $status ) {
// If the composer command failed there's no point in trying the same on `common`
return;
// Prompt for execution within subdirectories.
foreach ( $sub_directories as $dir ) {
if (
file_exists( tric_plugins_dir( "{$using}/{$dir}" ) )
&& ask( "\nWould you also like to run that {$base_command} command against {$dir}?", 'yes' )
) {
$targets[] = $dir;
}
}

if ( ! file_exists( tric_plugins_dir( "{$using}/common" ) ) ) {
return;
}
// Build the command process.
$command_process = static function( $target ) use ( $using, $base_command, $command, $sub_directories ) {
$prefix = "{$base_command}:" . light_cyan( $target );

if ( ask( "\nWould you like to run that npm command against common?", 'yes' ) ) {
tric_switch_target( "{$using}/common" );
// Execute command as the parent.
if ( 'target' !== $target ) {
tric_switch_target( "{$using}/{$target}" );
$prefix = "{$base_command}:" . yellow( $target );
}

echo light_cyan( "Temporarily using " . tric_target() . "\n" );
$status = tric_realtime()( array_merge( [ 'run', '--rm', $base_command ], $command ), $prefix );

tric_realtime()( array_merge( [ 'run', '--rm', 'npm' ], $command ) );
if ( 'target' !== $target ) {
tric_switch_target( $using );
}

tric_switch_target( $using );
return pcntl_exit( $status );
};

echo light_cyan( "Using " . tric_target() . " once again\n" );
$pool = [];

// Build the pool with a target/container/command-specific key.
foreach ( $targets as $target ) {
$clean_command = implode( ' ', $command );

$pool[ "{$target}:{$base_command}:{$clean_command}" ] = [
'target' => $target,
'container' => $base_command,
'command' => $command,
'process' => $command_process,
];
}

return $pool;
}

/**
* Run a command using the `composer` service.
* Executes a pool of commands in parallel.
*
* If `common` is available in the target and the command dos not fail, then the user will be prompted to run the same
* command on `common`.
*
* @param array<string> $command The `composer` command to run, e.g. `['install','--no-dev']` in array format.
* @param array $pool Pool of processes to execute in parallel.
* $pool[] = [
* 'target' => (string) Tric target.
* 'container' => (string) Container on which to execute the command.
* 'command' => (array) The command to run, e.g. `['install', '--save-dev']` in array format.
* 'process' => (closure) The function to execute for each Tric target.
* ]
* @return int Result of combined command execution.
*/
function tric_run_composer_command( array $command ) {
$using = tric_target();
echo light_cyan( "Using {$using}\n" );

setup_id();
$status = tric_realtime()( array_merge( [ 'run', '--rm', 'composer' ], $command ) );

if ( 0 !== $status ) {
// If the composer command failed there's no point in trying the same on `common`
return;
}

if ( ! file_exists( tric_plugins_dir( "{$using}/common" ) ) ) {
return;
function execute_command_pool( $pool ) {
if ( ! $pool ) {
return 0;
}

if ( ask( "\nWould you like to run that composer command against common?", 'yes' ) ) {
tric_switch_target( "{$using}/common" );

echo light_cyan( "Temporarily using " . tric_target() . "\n" );

tric_realtime()( array_merge( [ 'run', '--rm', 'composer' ], $command ) );
$using = tric_target();

if ( count( $pool ) > 1 ) {
$status = parallel_process( $pool );
tric_switch_target( $using );

echo light_cyan( "Using " . tric_target() . " once again\n" );
return $status;
}

$pool_item = reset( $pool );

return $pool_item['process']( $pool_item['target'] );
}

/**
Expand Down

0 comments on commit ff382a4

Please sign in to comment.