-
Notifications
You must be signed in to change notification settings - Fork 2
/
cps2pc
executable file
·259 lines (222 loc) · 9.92 KB
/
cps2pc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
#!/usr/bin/env python
import argparse
import cps
import os
import re
import subprocess
import sys
from collections import OrderedDict
from cps import iterate
_default_prefix = '${pcfiledir}/../..'
_supported_compilers = ['gcc', 'clang']
_supported_kernels = ['cygwin', 'linux', 'darwin']
# Compile definitions
_re_definitions = re.compile('(!)?(.*)')
_unix_definition_prefixes = {None: '-D', '!': '-U'}
_compilers_definition_prefixes = {
'gcc': _unix_definition_prefixes, 'clang': _unix_definition_prefixes}
# Compile features
_re_we_features = re.compile('((no)?(warn|error):(error$)?)(.*)')
_W_we_prefixes = {
'warn:error': '-Werror', 'nowarn:error': '-Wno-error', 'warn:': '-W',
'nowarn:': '-Wno-', 'error:': '-Werror=', 'noerror:': '-Wno-error='}
_compilers_we_prefixes = {'gcc': _W_we_prefixes, 'clang': _W_we_prefixes}
_supported_compiler_feature_list = [
'c99', 'c11', 'c++03', 'c++11', 'c++14', 'c++17', 'c++20', 'gnu']
_supported_gcc_features = {
x: '--std=%s' % x for x in _supported_compiler_feature_list}
_supported_clang_features = _supported_gcc_features
_supported_compile_features = {
'gcc': _supported_gcc_features, 'clang': _supported_clang_features}
# Link features
_supported_link_features = {x: {'threads': '-lpthread'}
for x in _supported_kernels}
#------------------------------------------------------------------------------
def warn(component, msg, *args, **kwargs):
pn = os.path.basename(sys.argv[0])
cn = component.name
sys.stderr.write('{}: warning: '.format(pn))
sys.stderr.write(msg.format(*args, **kwargs))
sys.stderr.write(
'\n{}: warning: while processing component \'{}\'\n'.format(pn, cn))
#------------------------------------------------------------------------------
def get_link_features(component, platform):
features = []
for feature in component.link_features:
try:
features.append(_supported_link_features[platform][feature])
except KeyError:
warn(component, 'unknown link feature \'{}\' on \'{}\'',
feature, platform)
return features
#------------------------------------------------------------------------------
def get_compile_features(component, compiler):
features = []
for feature in component.compile_features:
try:
# Check feature map first
features.append(_supported_compile_features[compiler][feature])
except KeyError as k:
# Warning specifiers are too divers to enumerate; we detect them
# with a regular expression instead
ma = _re_we_features.match(feature)
if ma is not None:
we_prefix = _compilers_we_prefixes[compiler][ma.group(1)]
features.append(we_prefix + ma.group(5))
continue
# Not an enumerated feature or a warning specifier; don't know what
# it is, so spit out a warning
warn(component, 'unknown compile feature \'{}\' on \'{}\'',
feature, compiler)
return features
#------------------------------------------------------------------------------
def get_definitions_flags(compile_definitions, compiler):
definitions = []
for definition in compile_definitions:
try:
ma = _re_definitions.match(definition)
# No need to test result `ma` as regex catches everything.
prefix = _compilers_definition_prefixes[compiler][ma.groups()[0]]
definitions.append(prefix + ma.groups()[1])
except KeyError:
# This should never happen as the list of supported compilers
# is hard-coded and the definitions either start with `!` or don't.
# If we are here, there is a bug.
raise Exception('Problem reading definition argument `%s`. '
'Most likely a bug in this script.' % definition)
return definitions
#------------------------------------------------------------------------------
def get_platform(package):
try:
return package.platform.kernel
except AttributeError:
try:
output = subprocess.check_output(['uname', '-s'])
return output.strip().lower()
except OSError:
raise Exception('Unable to detect current platform.')
#------------------------------------------------------------------------------
def get_kind_pc_dictionary(kind, location):
kind_dict = {}
if kind == 'exe':
raise Exception('executable')
elif kind in ['dylib', 'static'] and location:
# Full path is specified directly without any flag.
kind_dict['Libs'] = [' %s' % location]
elif kind == 'jar':
kind_dict['classpath'] = location
elif kind == 'interface':
pass
else:
raise Exception(kind)
return kind_dict
#------------------------------------------------------------------------------
def write_pc(pc_dict, filename, prefix):
def list_to_str(l):
return ' '.join(l)
convert = {'unicode': unicode, 'str': str, 'list': list_to_str}
with open(filename, 'w') as f:
for k, v in iterate(pc_dict):
f.write('%s: %s\n' % (k, convert[type(v).__name__](v).replace(
'@prefix@', prefix)))
#------------------------------------------------------------------------------
def remove_empty_values(pc_dict):
# Record empty keys (so we aren't modifying the dict during iteration)
empty = set()
for k, v in iterate(pc_dict):
if not v:
empty.add(k)
# Remove empty keys
for k in empty:
pc_dict.pop(k)
#------------------------------------------------------------------------------
def write_targets_config(package, output, single_output, compiler, prefix):
platform = get_platform(package)
if single_output and len(package.components) != 1:
raise Exception('Cannot write to file: '
'input CPS describes more than one component. '
'Use --output-dir instead.')
for component_name, component in iterate(package.components):
setattr(component, 'name', component_name)
# Ignored CPS tags (not supported by pkgconfig):
# * Link-Languages
# * Configurations
pc_dict = OrderedDict()
pc_dict['Name'] = component_name
pc_dict['Version'] = package.version
# Handle supplemental schema
for element in ['Description', 'Website']:
pc_dict[element] = package.data.get(element)
# Add libraries
pc_dict['Libs'] = []
try:
# In theory, we care about both `link_location` and `location`
# properties of the component. In practice, Link-Location
# supersedes Location for our purposes, and if the CPS did not
# specify Link-Location, PyCPS fills `link_location` from Location.
pc_dict.update(
get_kind_pc_dictionary(component.kind, component.link_location)
)
except Exception as e:
warn(component,
'component kind \'{}\' is not supported by pkgconfig',
component.kind)
warn(component, 'no output will be generated for this component')
continue
pc_dict['Libs'] += component.link_libraries
if component.link_requires:
warn(component, 'Link-Requires is being ignored, '
'as it is not supported at this time')
pc_dict['Libs'] += component.link_flags[None]
pc_dict['Libs'] += get_link_features(component, platform)
# Add compile flags
pc_dict['Cflags'] = []
pc_dict['Cflags'] += list(component.compile_flags[None])
pc_dict['Cflags'] += ['-I%s' % x for x in component.includes[None]]
pc_dict['Cflags'] += get_definitions_flags(
component.definitions[None], compiler)
pc_dict['Cflags'] += get_compile_features(component, compiler)
# Remove package name from possibly-qualified component names. This is
# a limited implementation that may not work in all cases and is likely
# to be improved in the future.
pc_dict['Requires'] = [x.split(':')[-1] for x in component.requires]
# Clean up and write .pc
remove_empty_values(pc_dict)
if single_output:
filename = output
else:
filename = os.path.join(output, component_name + '.pc')
write_pc(pc_dict, filename, prefix)
#------------------------------------------------------------------------------
def main(args):
parser = argparse.ArgumentParser()
parser.add_argument('input', metavar='CPS_FILE', type=str,
help='Input CPS file')
outarg = parser.add_mutually_exclusive_group(required=True)
outarg.add_argument('--output-dir', metavar='OUTPUT_DIRECTORY', type=str,
help='Output directory for pkgconf files')
outarg.add_argument('--output-file', metavar='OUTPUT_FILE',
type=str, help='Output file names for pkgconf files')
parser.add_argument('--compiler', choices=_supported_compilers,
default=_supported_compilers[0],
help='Targeted compiler')
parser.add_argument('--prefix', type=str,
default=_default_prefix,
help='Use given prefix instead of ' + _default_prefix)
# Parse arguments
args = parser.parse_args(args)
# Convert arguments to Boolean to simplify processing later
if args.output_file:
single_output = True
output = args.output_file
else:
single_output = False
output = args.output_dir
# Read input CPS
package = cps.read(args.input, canonicalize=True)
# Write output file(s)
write_targets_config(package, output, single_output,
args.compiler, args.prefix)
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
if __name__ == "__main__":
main(sys.argv[1:])