@@ -31,6 +31,7 @@ pub struct Entry {
3131 pub homepage : Vec < String > ,
3232 pub license : Vec < String > ,
3333 pub maintainer : Vec < String > ,
34+ #[ serde( skip_serializing_if = "Vec::is_empty" ) ]
3435 pub note : Vec < String > ,
3536 pub category : Vec < String > ,
3637 pub provides : Vec < String > ,
@@ -70,57 +71,36 @@ pub struct Binary {
7071 pub link_as : Option < String > ,
7172}
7273
73- /// Drop a leading archive-root component.
74+ /// Whether an installed file is a desktop-integration resource rather than an
75+ /// executable.
7476///
75- /// Install paths are written against the archive as published, but soar
76- /// promotes a single top-level directory away before locating binaries. A
77- /// path with no directory component is already at the root.
78- fn strip_archive_root ( path : & str ) -> String {
79- match path. split_once ( '/' ) {
80- Some ( ( _, rest) ) if !rest. is_empty ( ) => rest. to_string ( ) ,
81- _ => path. to_string ( ) ,
82- }
77+ /// soar treats a non-empty `binaries` as the complete list of things to link,
78+ /// so one icon or desktop entry in there stops the actual binary being found.
79+ fn is_resource ( name : & str ) -> bool {
80+ let ext = name. rsplit_once ( '.' ) . map ( |( _, e) | e. to_ascii_lowercase ( ) ) ;
81+ matches ! ( ext. as_deref( ) , Some ( "desktop" | "png" | "svg" | "xpm" | "ico" ) )
8382}
8483
8584/// Expand the two template variables an install path may carry.
8685fn expand_arch ( s : & str , version : & str , arch : & str ) -> String {
8786 s. replace ( "${version}" , version) . replace ( "${arch}" , arch)
8887}
8988
90- /// Rebuild the user-facing note list from the structured fields .
89+ /// Notes a user needs told, and nothing else .
9190///
92- /// Notes are presentation, so they are derived here rather than stored once
93- /// per package in the tree.
94- fn render_notes ( p : & PkgToml , src : & str ) -> Vec < String > {
95- let explicit = & p. pkg . note ;
96- let is_prov = |n : & String | n. starts_with ( "Official binary from" ) || n. starts_with ( "Fetched from" ) ;
97-
98- // A package may carry its own provenance wording; it wins over the
99- // derived line and keeps the leading position.
100- let mut out: Vec < String > = explicit. iter ( ) . filter ( |n| is_prov ( n) ) . cloned ( ) . collect ( ) ;
101- if out. is_empty ( ) {
102- out. push ( if p. pkg . kind . as_deref ( ) == Some ( "appimage" ) {
103- format ! ( "Fetched from Pre Built Community Created AppImage. Check/Report @ {src}" )
104- } else {
105- format ! ( "Official binary from {src}" )
106- } ) ;
107- }
108-
109- if p. pkg . portable {
110- let suffix = if p. pkg . kind . as_deref ( ) == Some ( "appimage" ) {
111- "Works on AnyLinux"
112- } else {
113- "Portable Static Binary"
114- } ;
115- out. push ( format ! ( "[PORTABLE] ({suffix})" ) ) ;
116- } else {
91+ /// Provenance and portability restate `src_url` and `type`, which the entry
92+ /// already carries, so they are not repeated here as prose. Needing something
93+ /// from the host is the exception: it is a limitation rather than a property,
94+ /// and there is no other field carrying it.
95+ fn render_notes ( p : & PkgToml ) -> Vec < String > {
96+ let mut out = Vec :: new ( ) ;
97+ if !p. pkg . portable {
11798 out. push ( match & p. pkg . portable_reason {
11899 Some ( why) => format ! ( "[NOT PORTABLE] {why}" ) ,
119100 None => "[NOT PORTABLE]" . to_string ( ) ,
120101 } ) ;
121102 }
122-
123- out. extend ( explicit. iter ( ) . filter ( |n| !is_prov ( n) ) . cloned ( ) ) ;
103+ out. extend ( p. pkg . note . iter ( ) . cloned ( ) ) ;
124104 out
125105}
126106
@@ -136,7 +116,6 @@ pub fn generate(root: &Path, host: &str) -> (Vec<Entry>, Vec<String>) {
136116 }
137117 let fam = p. pkg . family . clone ( ) ;
138118 let srcs = p. src_urls ( ) ;
139- let src0 = srcs. first ( ) . cloned ( ) . unwrap_or_default ( ) ;
140119
141120 for v in & pkg. versions {
142121 let Some ( url) = v. url . get ( host) else { continue } ;
@@ -178,20 +157,23 @@ pub fn generate(root: &Path, host: &str) -> (Vec<Entry>, Vec<String>) {
178157 let nested = from. trim_start_matches ( "*/" ) . contains ( '/' ) ;
179158 ( base != * to || nested)
180159 && !to. eq_ignore_ascii_case ( "LICENSE" )
160+ && !is_resource ( to)
181161 && * from != "*"
182162 } )
183163 . map ( |( from, to) | Binary {
184164 // The index is generated per host, so templates
185165 // are expanded here rather than shipped for the
186166 // client to resolve.
187- // The archive root is promoted away before
188- // binaries are resolved, so publish the path
189- // relative to what remains.
190- source : strip_archive_root ( & expand_arch (
191- from,
192- & v. version ,
193- & arch_for_host,
194- ) ) ,
167+ // Published as written against the archive. An
168+ // archive with one top-level directory has it
169+ // promoted away before binaries are resolved, so
170+ // the client retries without the leading
171+ // component; stripping it here instead would
172+ // discard the only thing telling two
173+ // architectures apart in a multi-arch archive.
174+ source : expand_arch ( from, & v. version , & arch_for_host)
175+ . trim_start_matches ( "*/" )
176+ . to_string ( ) ,
195177 // Strip soar's provides markers; link_as is a
196178 // plain filename.
197179 link_as : Some (
@@ -211,6 +193,9 @@ pub fn generate(root: &Path, host: &str) -> (Vec<Entry>, Vec<String>) {
211193 . extra
212194 . iter ( )
213195 . filter ( |e| e. blake3 . is_some ( ) || e. sha256 . is_some ( ) )
196+ // A side file pinned per host belongs only to that host's
197+ // index; one without a host applies to all of them.
198+ . filter ( |e| e. host . as_deref ( ) . is_none_or ( |h| h == host) )
214199 . map ( |e| ExtraFile {
215200 url : e. url . clone ( ) ,
216201 to : e. to . clone ( ) ,
@@ -221,7 +206,7 @@ pub fn generate(root: &Path, host: &str) -> (Vec<Entry>, Vec<String>) {
221206
222207 {
223208 let prov = provides. clone ( ) ;
224- let mut note = render_notes ( p, & src0 ) ;
209+ let mut note = render_notes ( p) ;
225210 if let Some ( n) = & note_src {
226211 note = n. clone ( ) ;
227212 }
0 commit comments