11#!/usr/bin/env python3
22# -*- coding: utf-8 -*-
33
4+ import typing as tp
45import glob
56import shutil
67import logging
@@ -20,6 +21,13 @@ def _glob_one(p):
2021 logging .info (f'Glob { p } -> { res [0 ]} ' )
2122 return Path (res [0 ])
2223
24+ def _is_relative_to (a , b ):
25+ try :
26+ a .relative_to (b )
27+ return True
28+ except ValueError :
29+ return False
30+
2331emacs_version = str (_glob_one (APPDIR / 'bin/emacs-*' )).split ('-' )[- 1 ]
2432logging .info (f'Emacs version: { emacs_version } ' )
2533
@@ -56,56 +64,149 @@ def _glob_one(p):
5664shutil .copy2 (_glob_one ('/lib/*/libc.so.6' ), gccjit_lib_path / 'libc.so.6' )
5765os .symlink ('libc.so.6' , str (gccjit_lib_path / 'libc.so' ))
5866
59- # copy libraries
60- LIB_WHITELIST = [
61- 'libMagickCore-6.Q16' , # depend by emacs
62- 'libMagickWand-6.Q16' , # depend by emacs
63- 'libbz2' , # depend by MagickCore
64- 'libfftw3' , # depend by MagickCore
65- 'libgomp' , # depend by MagickWand
66- 'liblcms2' , # depend by emacs, MagickWand, MagickCore
67- 'liblqr-1' , # depend by MagickWand, MagickCore
68- 'libltdl' , # depend by MagickCore
69-
70- 'librsvg-2' , # depend by emacs
71- 'libcroco-0.6' , # depend by rsvg
72-
73- 'libgif' , # depend by emacs
74- 'libjansson' , # depend by emacs
75- 'libotf' , # depend by emacs
76- 'libsqlite3' , # depend by emacs
77- 'libtinfo' , # depend by emacs
78- 'libwebpdemux' , # depend by emacs
79- 'libwebp' , # depend by emacs
80-
81- 'libmpc' , # depend by libgccjit
82- 'libmpfr' , # depend by libgccjit
83-
84- # 'libdatrie', # thai
85-
86- # 'libpng16', # depend by emacs. but should be present in users' system by gtk, so do not include
87-
88- 'libxml2' , # depend by emacs. libxml 2.14+ uses a different soname
89- 'libicuuc' , # depend by libxml2
90- 'libicudata' , # depend by libicuuc
67+ # ---
68+ # process dynamic libs
69+ # ---
70+ class NeededLib :
71+
72+ def __init__ (self , name : str , resolved_path : Path ):
73+ self .name = name
74+ self .resolved_path = resolved_path
75+ self .needed_libs = []
76+
77+ def _collect (self , result ):
78+ for l in self .needed_libs :
79+ result [l .name ] = l .resolved_path
80+ l ._collect (result )
81+
82+ def collect (self ) -> tp .Dict [str , Path ]:
83+ '''
84+ Collect all nodes in current tree (except self).
85+ '''
86+ result = {}
87+ self ._collect (result )
88+ return result
89+
90+ def _trim (self , name_prefix , trimmed_names : tp .Set [str ]):
91+ kept_libs = []
92+ for x in self .needed_libs :
93+ if not x .name .startswith (name_prefix ):
94+ kept_libs .append (x )
95+ else :
96+ trimmed_names .add (x .name )
97+ trimmed_names .update (x .collect ().keys ())
98+ self .needed_libs = kept_libs
99+ for x in self .needed_libs :
100+ x ._trim (name_prefix , trimmed_names )
101+
102+ def trim (self , name_prefix ) -> tp .Set [str ]:
103+ '''
104+ Delete node whose name starts with name_prefix, along with all their children.
105+ '''
106+ trimmed_names = set ()
107+ self ._trim (name_prefix , trimmed_names )
108+ return trimmed_names
109+
110+ def _leading_space_count (s ) -> int :
111+ n = 0
112+ while n < len (s ) and s [n ] == ' ' :
113+ n = n + 1
114+ return n
115+
116+ def _parse_lddtree (lddtree_output : str ) -> NeededLib :
117+ lines = lddtree_output .rstrip ().splitlines ()
118+ assert not lines [0 ].startswith (' ' )
119+ line_i = 1
120+
121+ def _parse_node (node , expected_level ):
122+ nonlocal line_i
123+ while line_i < len (lines ):
124+ line = lines [line_i ]
125+ level = _leading_space_count (line ) // 4
126+
127+ if level > expected_level :
128+ assert level == expected_level + 1
129+ assert len (node .needed_libs ) > 0
130+ _parse_node (node .needed_libs [- 1 ], expected_level + 1 )
131+ continue
132+
133+ if level < expected_level :
134+ return
135+
136+ parts = line .strip ().split (' => ' )
137+ assert len (parts ) == 2
138+ node .needed_libs .append (
139+ NeededLib (name = parts [0 ], resolved_path = Path (parts [1 ]))
140+ )
141+ line_i = line_i + 1
142+
143+ root = NeededLib (name = '' , resolved_path = Path ())
144+ _parse_node (root , 1 )
145+ return root
146+
147+
148+ lddtree_output = subprocess .check_output (
149+ ['lddtree' , '-a' , str (APPDIR / 'bin/emacs' )],
150+ universal_newlines = True ,
151+ )
152+ lddtree = _parse_lddtree (lddtree_output )
153+
154+
155+ # this is prefix
156+ LIB_BLACKLIST = [
157+ # GTK related
158+ 'libgtk-3.so' ,
159+ 'libgdk-3.so' ,
160+ 'libgdk_pixbuf-2.0.so' ,
161+ 'libgio-2.0.so' ,
162+ 'libgobject-2.0.so' ,
163+ 'libglib-2.0.so' ,
164+ # X related
165+ 'libX' , # prefix
166+ 'libxcb.so' ,
167+ 'libxcb-shape.so' ,
168+ 'libSM.so' ,
169+ 'libICE.so' ,
170+ # GUI base system
171+ 'libpango' ,
172+ 'libcairo.so' ,
173+ 'libfreetype.so' ,
174+ 'libfontconfig.so' ,
175+ 'libharfbuzz.so' ,
176+ 'libGL' ,
177+ 'libOpenGL' ,
178+ 'libEGL' ,
179+ 'libdbus-1.so' ,
180+ # base system
181+ 'libgcc_s.so' ,
182+ 'libgnutls.so' ,
183+ 'libstdc++.so.6' ,
184+ # glibc
185+ 'libpthread.so' ,
186+ 'librt.so' ,
187+ 'libc.so' ,
188+ 'libc_' ,
189+ 'libdl.so' ,
190+ 'libresolv.so' ,
191+ 'libz.so' ,
192+ 'libm.so' ,
193+ 'libanl.so' ,
194+ 'libnss_' ,
195+ 'libutil.so' ,
91196]
92197
93- ldd_output = subprocess .check_output (['ldd' , str (APPDIR / 'bin/emacs' )], universal_newlines = True )
94- for line in ldd_output .splitlines ():
95- if '=>' not in line or 'not found' in line :
96- logging .warning (f'Unexpected ldd output: { line } ' )
97- continue
98- libpath = Path (line .split ()[2 ])
99- if not libpath .exists ():
100- logging .warning (f'Skipping non-exist library { libpath } ' )
101- continue
102- if libpath .parent .resolve () == (APPDIR / 'lib' ):
103- continue
104- libname = libpath .name .split ('.so' )[0 ]
105- if libname not in LIB_WHITELIST :
106- logging .info (f'Skipping non-whitelisted library { libpath } ' )
198+ # Remove lib from blacklist
199+ # also, remove all its children
200+ for name in LIB_BLACKLIST :
201+ trimmed_names = lddtree .trim (name )
202+ for trimmed_name in trimmed_names :
203+ logging .warning (f'Removed lib { trimmed_name } ' )
204+ lddtree .trim (trimmed_name )
205+
206+ for name , libpath in lddtree .collect ().items ():
207+ if _is_relative_to (libpath .resolve (), APPDIR ): # e.g. libgccjit
107208 continue
108- logging .info (f'Copying { libpath } ' )
209+ logging .info (f'Copying { name } ( { libpath } ) ' )
109210 dst_path = APPDIR / 'lib' / libpath .name
110211 shutil .copy2 (libpath , dst_path )
111212
0 commit comments