@@ -623,6 +623,10 @@ fn safe_user_install(
623623 std:: fs:: rename ( & content, & dest) ?;
624624
625625 patch_r_home_dir ( & dest) ?;
626+ if let Err ( e) = patch_renviron_tar ( & dest) {
627+ OUTPUT . warn ( & format ! ( "Could not set TAR in Renviron: {}" , e) ) ;
628+ warn ! ( "Could not set TAR in Renviron: {}" , e) ;
629+ }
626630 if let Err ( e) = shim_bundled_xcursor ( & dest) {
627631 OUTPUT . warn ( & format ! ( "Could not shim libXcursor: {}" , e) ) ;
628632 warn ! ( "Could not shim libXcursor: {}" , e) ;
@@ -682,6 +686,59 @@ fn patch_r_home_dir_script(content: &str, home: &str) -> Option<String> {
682686 Some ( out)
683687}
684688
689+ // The portable builds are configured on a machine where GNU tar is installed as
690+ // `gtar`, so their `etc/Renviron` has `TAR=${TAR-'/usr/bin/gtar'}`, a path that
691+ // does not exist on most Linux systems. Point `TAR` at `/usr/bin/tar` instead,
692+ // which is GNU tar on Linux.
693+ fn patch_renviron_tar ( dest : & Path ) -> Result < ( ) , Box < dyn Error > > {
694+ let renviron = dest. join ( "lib" ) . join ( "R" ) . join ( "etc" ) . join ( "Renviron" ) ;
695+ if !renviron. exists ( ) {
696+ debug ! ( "No {}, not patching TAR" , renviron. display( ) ) ;
697+ return Ok ( ( ) ) ;
698+ }
699+ let tar = preferred_tar ( ) ;
700+ let content = std:: fs:: read_to_string ( & renviron) ?;
701+ match patch_renviron_tar_content ( & content, tar) {
702+ Some ( patched) => {
703+ debug ! ( "Patching TAR in {} to {}" , renviron. display( ) , tar) ;
704+ // Writing to the existing file keeps its permissions.
705+ std:: fs:: write ( & renviron, patched) ?;
706+ }
707+ None => {
708+ debug ! ( "No TAR default to patch in {}" , renviron. display( ) ) ;
709+ }
710+ }
711+ Ok ( ( ) )
712+ }
713+
714+ // The tar `Renviron` should default to: `/usr/bin/tar`, unless it is missing and
715+ // the `/usr/bin/gtar` the build was configured with is actually there.
716+ fn preferred_tar ( ) -> & ' static str {
717+ if !Path :: new ( "/usr/bin/tar" ) . exists ( ) && Path :: new ( "/usr/bin/gtar" ) . exists ( ) {
718+ "/usr/bin/gtar"
719+ } else {
720+ "/usr/bin/tar"
721+ }
722+ }
723+
724+ // Set the `TAR` default in the contents of `etc/Renviron` to `tar`, keeping the
725+ // `${TAR-...}` fallback form, so a `TAR` from the environment still wins.
726+ // Returns `None` if there is no `TAR=` line, or it already has this default.
727+ fn patch_renviron_tar_content ( content : & str , tar : & str ) -> Option < String > {
728+ let want = format ! ( "TAR=${{TAR-'{}'}}" , tar) ;
729+ let mut lines: Vec < String > = content. lines ( ) . map ( |l| l. to_string ( ) ) . collect ( ) ;
730+ let idx = lines. iter ( ) . position ( |l| l. starts_with ( "TAR=" ) ) ?;
731+ if lines[ idx] == want {
732+ return None ;
733+ }
734+ lines[ idx] = want;
735+ let mut out = lines. join ( "\n " ) ;
736+ if content. ends_with ( '\n' ) {
737+ out. push ( '\n' ) ;
738+ }
739+ Some ( out)
740+ }
741+
685742// The name the bundled libX11 passes to `dlopen()`, and the name of the shim we
686743// put next to it. See `shim_bundled_xcursor()`.
687744const XCURSOR_SONAME : & str = "libXcursor.so.1" ;
@@ -2507,6 +2564,39 @@ Usage: /lib/ld-musl-x86_64.so.1 [options] [--] pathname\n";
25072564 assert ! ( patch_r_home_dir_script( "echo \" ${R_HOME_DIR}\" \n " , "/home/u/r/lib/R" ) . is_none( ) ) ;
25082565 }
25092566
2567+ // The relevant part of `etc/Renviron` of a portable build.
2568+ const RENVIRON : & str = "R_GZIPCMD=${R_GZIPCMD-'/usr/bin/gzip'}\n \
2569+ TAR=${TAR-'/usr/bin/gtar'}\n \
2570+ R_UNZIPCMD=${R_UNZIPCMD-'/usr/bin/unzip'}\n ";
2571+
2572+ #[ test]
2573+ fn patch_renviron_tar_content_replaces_gtar_default ( ) {
2574+ let out = patch_renviron_tar_content ( RENVIRON , "/usr/bin/tar" )
2575+ . expect ( "Renviron has a TAR default" ) ;
2576+ assert_eq ! (
2577+ out,
2578+ "R_GZIPCMD=${R_GZIPCMD-'/usr/bin/gzip'}\n \
2579+ TAR=${TAR-'/usr/bin/tar'}\n \
2580+ R_UNZIPCMD=${R_UNZIPCMD-'/usr/bin/unzip'}\n "
2581+ ) ;
2582+ }
2583+
2584+ #[ test]
2585+ fn patch_renviron_tar_content_leaves_matching_default ( ) {
2586+ // Already the wanted default, or `gtar` on a system that only has that.
2587+ let tar = RENVIRON . replace ( "gtar" , "tar" ) ;
2588+ assert ! ( patch_renviron_tar_content( & tar, "/usr/bin/tar" ) . is_none( ) ) ;
2589+ assert ! ( patch_renviron_tar_content( RENVIRON , "/usr/bin/gtar" ) . is_none( ) ) ;
2590+ }
2591+
2592+ #[ test]
2593+ fn patch_renviron_tar_content_without_tar_line ( ) {
2594+ assert ! ( patch_renviron_tar_content( "R_GZIPCMD=/usr/bin/gzip\n " , "/usr/bin/tar" ) . is_none( ) ) ;
2595+ // `R_TAR` and a reference to `${TAR}` are not `TAR` assignments.
2596+ assert ! ( patch_renviron_tar_content( "R_TAR=/usr/bin/gtar\n " , "/usr/bin/tar" ) . is_none( ) ) ;
2597+ assert ! ( patch_renviron_tar_content( "echo ${TAR}\n " , "/usr/bin/tar" ) . is_none( ) ) ;
2598+ }
2599+
25102600 #[ test]
25112601 fn link_points_into_admin_root_matches_opt_r ( ) {
25122602 assert ! ( link_points_into_admin_root( Path :: new( "/opt/R/4.6.0/bin/R" ) ) ) ;
0 commit comments