@@ -2068,6 +2068,49 @@ fn package_stub_exclusions(package_name: &str) -> HashSet<String> {
20682068 . unwrap_or_default ( )
20692069}
20702070
2071+ fn imagemagick_stub_exclusions (
2072+ plan : & InstallPlan ,
2073+ current : & [ ( String , PathBuf ) ] ,
2074+ ) -> HashSet < String > {
2075+ if !should_only_stub_magick ( plan) {
2076+ return HashSet :: new ( ) ;
2077+ }
2078+
2079+ current
2080+ . iter ( )
2081+ . filter_map ( |( name, _) | ( name != "magick" ) . then ( || name. clone ( ) ) )
2082+ . collect ( )
2083+ }
2084+
2085+ fn should_only_stub_magick ( plan : & InstallPlan ) -> bool {
2086+ match plan. root_formula . as_str ( ) {
2087+ "imagemagick-full" => true ,
2088+ "imagemagick" => installed_formula_major_version ( plan) . is_some_and ( |major| major >= 7 ) ,
2089+ _ => false ,
2090+ }
2091+ }
2092+
2093+ fn installed_formula_major_version ( plan : & InstallPlan ) -> Option < u64 > {
2094+ let receipt = load_package_receipt ( & plan. root_receipt_path ( ) )
2095+ . ok ( )
2096+ . flatten ( ) ?;
2097+ let PackageReceiptSource :: Formula { root_formula } = receipt. source else {
2098+ return None ;
2099+ } ;
2100+ if root_formula != plan. root_formula {
2101+ return None ;
2102+ }
2103+ parse_homebrew_major_version ( & receipt. version )
2104+ }
2105+
2106+ fn parse_homebrew_major_version ( version : & str ) -> Option < u64 > {
2107+ let trimmed = version. strip_prefix ( 'v' ) . unwrap_or ( version) ;
2108+ trimmed
2109+ . split ( |ch : char | !ch. is_ascii_digit ( ) )
2110+ . find ( |part| !part. is_empty ( ) )
2111+ . and_then ( |major| major. parse ( ) . ok ( ) )
2112+ }
2113+
20712114fn versioned_python_stub_exclusions ( formula : & str ) -> HashSet < String > {
20722115 let Some ( ( major, minor) ) = parse_python_formula_version ( formula) else {
20732116 return HashSet :: new ( ) ;
@@ -3635,7 +3678,6 @@ fn sync_stubs(
36353678
36363679 fs:: create_dir_all ( USR_LOCAL_BIN )
36373680 . map_err ( |err| format ! ( "failed to create {}: {err}" , USR_LOCAL_BIN ) ) ?;
3638- let excluded_stubs = formula_stub_exclusions ( & plan. root_formula ) ;
36393681 let current = if plan. mode == Mode :: I {
36403682 let manifest = load_root_executable_manifest ( & plan. root_executables_manifest_path ( ) ) ?;
36413683 if manifest. stubs . is_empty ( ) {
@@ -3649,6 +3691,8 @@ fn sync_stubs(
36493691 } else {
36503692 collect_root_executables ( & plan. stable_root ) ?
36513693 } ;
3694+ let mut excluded_stubs = formula_stub_exclusions ( & plan. root_formula ) ;
3695+ excluded_stubs. extend ( imagemagick_stub_exclusions ( plan, & current) ) ;
36523696 let current = filter_stub_executables ( current, & excluded_stubs) ;
36533697 for stub in previous_stubs {
36543698 if !current. iter ( ) . any ( |( name, _) | name == stub) {
@@ -7674,6 +7718,14 @@ long_prefix = re.compile(r'/opt/python@3.12/[0-9\\._abrc]+')\n"
76747718 ) ;
76757719 }
76767720
7721+ #[ test]
7722+ fn formula_stub_exclusions_alias_ffmpeg_to_ffmpeg_full ( ) {
7723+ assert_eq ! (
7724+ formula_stub_exclusions( "ffmpeg" ) ,
7725+ formula_stub_exclusions( "ffmpeg-full" )
7726+ ) ;
7727+ }
7728+
76777729 #[ test]
76787730 fn formula_stub_exclusions_cover_dead_python_tools ( ) {
76797731 let exclusions = formula_stub_exclusions ( "python@3.12" ) ;
@@ -7698,6 +7750,93 @@ long_prefix = re.compile(r'/opt/python@3.12/[0-9\\._abrc]+')\n"
76987750 assert ! ( !exclusions. contains( "pip3.12" ) ) ;
76997751 }
77007752
7753+ #[ test]
7754+ fn imagemagick_full_only_stubs_magick ( ) {
7755+ let temp = TempDir :: new ( ) . unwrap ( ) ;
7756+ let plan = InstallPlan {
7757+ mode : Mode :: I ,
7758+ package_name : "imagemagick-full" . to_string ( ) ,
7759+ root_formula : "imagemagick-full" . to_string ( ) ,
7760+ stable_root : temp. path ( ) . join ( "opt/imagemagick-full" ) ,
7761+ install_root : temp. path ( ) . join ( "opt/imagemagick-full" ) ,
7762+ tmp_root : temp. path ( ) . join ( "tmp" ) ,
7763+ } ;
7764+ let current = vec ! [
7765+ ( "convert" . to_string( ) , PathBuf :: from( "/tmp/bin/convert" ) ) ,
7766+ ( "magick" . to_string( ) , PathBuf :: from( "/tmp/bin/magick" ) ) ,
7767+ ( "identify" . to_string( ) , PathBuf :: from( "/tmp/bin/identify" ) ) ,
7768+ ] ;
7769+
7770+ assert_eq ! (
7771+ imagemagick_stub_exclusions( & plan, & current) ,
7772+ HashSet :: from( [ "convert" . to_string( ) , "identify" . to_string( ) ] )
7773+ ) ;
7774+ }
7775+
7776+ #[ test]
7777+ fn imagemagick_v7_only_stubs_magick ( ) {
7778+ let temp = TempDir :: new ( ) . unwrap ( ) ;
7779+ let plan = InstallPlan {
7780+ mode : Mode :: I ,
7781+ package_name : "imagemagick" . to_string ( ) ,
7782+ root_formula : "imagemagick" . to_string ( ) ,
7783+ stable_root : temp. path ( ) . join ( "opt/imagemagick" ) ,
7784+ install_root : temp. path ( ) . join ( "opt/imagemagick" ) ,
7785+ tmp_root : temp. path ( ) . join ( "tmp" ) ,
7786+ } ;
7787+ write_package_receipt (
7788+ & plan. root_receipt_path ( ) ,
7789+ & PackageReceipt {
7790+ package_name : "imagemagick" . to_string ( ) ,
7791+ version : "7.1.2_3" . to_string ( ) ,
7792+ source : PackageReceiptSource :: Formula {
7793+ root_formula : "imagemagick" . to_string ( ) ,
7794+ } ,
7795+ } ,
7796+ )
7797+ . unwrap ( ) ;
7798+ let current = vec ! [
7799+ ( "convert" . to_string( ) , PathBuf :: from( "/tmp/bin/convert" ) ) ,
7800+ ( "magick" . to_string( ) , PathBuf :: from( "/tmp/bin/magick" ) ) ,
7801+ ( "mogrify" . to_string( ) , PathBuf :: from( "/tmp/bin/mogrify" ) ) ,
7802+ ] ;
7803+
7804+ assert_eq ! (
7805+ imagemagick_stub_exclusions( & plan, & current) ,
7806+ HashSet :: from( [ "convert" . to_string( ) , "mogrify" . to_string( ) ] )
7807+ ) ;
7808+ }
7809+
7810+ #[ test]
7811+ fn imagemagick_v6_keeps_legacy_stubs ( ) {
7812+ let temp = TempDir :: new ( ) . unwrap ( ) ;
7813+ let plan = InstallPlan {
7814+ mode : Mode :: I ,
7815+ package_name : "imagemagick" . to_string ( ) ,
7816+ root_formula : "imagemagick" . to_string ( ) ,
7817+ stable_root : temp. path ( ) . join ( "opt/imagemagick" ) ,
7818+ install_root : temp. path ( ) . join ( "opt/imagemagick" ) ,
7819+ tmp_root : temp. path ( ) . join ( "tmp" ) ,
7820+ } ;
7821+ write_package_receipt (
7822+ & plan. root_receipt_path ( ) ,
7823+ & PackageReceipt {
7824+ package_name : "imagemagick" . to_string ( ) ,
7825+ version : "6.9.13_7" . to_string ( ) ,
7826+ source : PackageReceiptSource :: Formula {
7827+ root_formula : "imagemagick" . to_string ( ) ,
7828+ } ,
7829+ } ,
7830+ )
7831+ . unwrap ( ) ;
7832+ let current = vec ! [
7833+ ( "convert" . to_string( ) , PathBuf :: from( "/tmp/bin/convert" ) ) ,
7834+ ( "magick" . to_string( ) , PathBuf :: from( "/tmp/bin/magick" ) ) ,
7835+ ] ;
7836+
7837+ assert ! ( imagemagick_stub_exclusions( & plan, & current) . is_empty( ) ) ;
7838+ }
7839+
77017840 #[ test]
77027841 fn stage_formula_merges_dependency_into_i_root ( ) {
77037842 let temp = TempDir :: new ( ) . unwrap ( ) ;
0 commit comments