Fix and clean up libcoap build
[iotivity.git] / extlibs / libcoap / SConscript
1 ################################################################################
2 #
3 # Copyright 2016 Intel Corporation
4 #
5 #
6 #
7 # Licensed under the Apache License, Version 2.0 (the "License");
8 # you may not use this file except in compliance with the License.
9 # You may obtain a copy of the License at
10 #
11 #      http://www.apache.org/licenses/LICENSE-2.0
12 #
13 # Unless required by applicable law or agreed to in writing, software
14 # distributed under the License is distributed on an "AS IS" BASIS,
15 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 # See the License for the specific language governing permissions and
17 # limitations under the License.
18 #
19 ################################################################################
20
21 ##
22 # 'libcoap' script to make sure LibCoAP library is "installed"
23 ##
24
25 import os
26 import subprocess
27 import SCons.Util
28
29 Import('env')
30
31 libcoap_env = env.Clone()
32 target_os = libcoap_env.get('TARGET_OS')
33 src_dir = libcoap_env.get('SRC_DIR')
34 ca_transport = libcoap_env.get('TARGET_TRANSPORT')
35 with_tcp = libcoap_env.get('WITH_TCP')
36 with_upstream_libcoap = libcoap_env.get('WITH_UPSTREAM_LIBCOAP')
37
38 # Temporary LibCoAP URL is a fork of the original.
39 # Once a pull request is merged, change this back to the obgm original below.
40 libcoap_repo_url = 'https://github.com/dthaler/libcoap'
41 #libcoap_repo_url    = 'https://github.com/obgm/libcoap'
42
43 ######################################################################
44 # Check for, and possibly update libcoap from git
45 ######################################################################
46 # The libcoap tag here must match the one in extlibs/libcoap/prep.sh.
47 libcoap_version = 'IoTivity-1.4'
48 libcoap_dir = os.path.join(src_dir, 'extlibs', 'libcoap', 'libcoap')
49
50 # git commands
51 libcoap_checkout_cmd = 'git clone ' + libcoap_repo_url + '.git extlibs/libcoap/libcoap -b ' + libcoap_version
52 libcoap_update_cmd = 'git fetch --tags && git checkout -f ' + libcoap_version
53 libcoap_tag_cmd = 'git tag -l ' + libcoap_version
54 libcoap_sync_cmd = 'git checkout -f ' + libcoap_version
55 libcoap_chdir_cmd = 'cd ' + libcoap_dir
56
57 # define msg strings here so code sequence is readable
58 err_nocoap = '''
59 *********************************** Error: ************************************
60 * Please download libcoap using the following command:
61 *     $ %s
62 *******************************************************************************
63 ''' % libcoap_checkout_cmd
64
65 info_oldcoap = '''
66 ******************************* Info: *****************************************
67 * Your libCoAP repo is not up to date with the latest version we require (%s).
68 *******************************************************************************
69 ''' % libcoap_version
70
71 info_updatingcoap = '''
72 ******************************* Info: *****************************************
73 * Automatically updating libcoap to version %s.
74 *******************************************************************************
75 ''' % libcoap_version
76
77 info_updatecoap = '''
78 ******************************* Info: *****************************************
79 * Please update using the following commands:
80 *   %s
81 *   %s
82 *******************************************************************************
83 ''' % (libcoap_chdir_cmd, libcoap_update_cmd)
84
85 info_forkedcoap = '''
86 ******************************* Info: *****************************************
87 * Using FORKED copy of libCoap located in:
88 * resource/csdk/connectivity/lib/libcoap-4.1.1
89 *******************************************************************************
90 '''
91
92 if with_upstream_libcoap == '0':
93     print(info_forkedcoap)
94 else:
95     # If using the github libcoap, check for correct version
96     # Right now this script assumes the revision is a tag, not a branch or
97     # an arbitrary commit. If this changes, update the check below, or else
98     # the script will always conclude the repo is not up to date because a tag
99     # with that name does not exist.
100     print('*** Checking for installation of libCoAP %s ***' % libcoap_version)
101     if not os.path.exists(libcoap_dir):
102         Exit(err_nocoap)
103
104     # Tizen uses its own process to prepare the libcoap repo in gbsbuild.sh
105     # and cannot use git during the build. That process removes .git.
106     # Do the git checks only if .git is present.
107     if os.path.exists(libcoap_dir + '/.git/HEAD'):
108         start_dir = os.getcwd()
109         os.chdir(libcoap_dir)
110         out = subprocess.check_output(libcoap_tag_cmd, shell=True).rstrip()
111         if libcoap_version not in SCons.Util.to_String(out):
112             print(info_oldcoap)
113             if libcoap_env.get('AUTOMATIC_UPDATE'):
114                 print(info_updatingcoap)
115                 try:
116                     retcode = subprocess.call(libcoap_update_cmd, shell=True)
117                     if retcode:
118                         Exit("libcoap: update failed: " + str(retcode))
119                 except OSError as e:
120                     Exit("libcoap: execution failed: " + e)
121             else:
122                 Exit(info_updatecoap)
123         # to be pedantic, always checkout to the tag
124         retcode = subprocess.call(libcoap_sync_cmd, shell=True)
125         if retcode:
126             Exit("libcoap: checkout failed: " + str(retcode))
127         os.chdir(start_dir)
128
129 ######################################################################
130 # Build libCoAP
131 ######################################################################
132 # Build flags
133 if target_os not in ['windows', 'winrt', 'msys_nt']:
134     # libcoap uses ipv6 features from <netinet/in.h> which only
135     # turn on if __GNU_SOURCE is defined
136     libcoap_env.AppendUnique(CPPDEFINES=['WITH_POSIX', '_GNU_SOURCE'])
137     libcoap_env.AppendUnique(CFLAGS=['-std=gnu99','-fPIC'])
138
139 if target_os not in ['windows', 'winrt']:
140     libcoap_env.AppendUnique(CFLAGS=['-Wall', '-ffunction-sections',
141             '-fdata-sections', '-fno-exceptions'])
142
143 if target_os == 'msys_nt':
144     libcoap_env.AppendUnique(CPPDEFINES=['_DEFAULT_SOURCE'])
145     libcoap_env.AppendUnique(CFLAGS=['-std=c99'])
146
147 if target_os in ['linux', 'tizen', 'android', 'ios', 'windows']:
148     if with_tcp == True:
149         libcoap_env.AppendUnique(CPPDEFINES=['WITH_TCP', 'WITH_WS'])
150
151 if target_os in ['linux', 'tizen', 'android']:
152     libcoap_env.AppendUnique(LIBS=['log'])
153     if 'BLE' in ca_transport or 'BT' in ca_transport or 'ALL' in ca_transport:
154         libcoap_env.AppendUnique(CPPDEFINES=['WITH_TCP'])
155
156 # Remove -Werror build flag when building the 'coap' library  it
157 # is external code. see IOT-2539
158 while '-Werror' in libcoap_env['CCFLAGS']: libcoap_env['CCFLAGS'].remove('-Werror')
159 if env['CC'] == 'cl':
160     # In external code, don't fail on warnings:
161     #  - warning C4267: conversion from size_t to unsigned short, possible loss of data
162     #  - warning C4244: '=' : conversion from 'size_t' to 'unsigned short', possible loss of data
163     # TODO: fix libcoap fork, we introduced this error, it isn't from upstream
164     libcoap_env.AppendUnique(CCFLAGS=['/wd4267', '/wd4244'])
165
166 ######################################################################
167 # Source files and Target(s)
168 ######################################################################
169 if with_upstream_libcoap == '0':
170     # For bring up purposes only, the forked version will live here.
171     libcoap_src_root = '#/resource/csdk/connectivity/lib/libcoap-4.1.1/'
172     libcoap_src = Glob(libcoap_src_root + '*.c')
173 else:
174     # We need to generate a pair of headers to describe our config
175     coap_h_pc_file = os.path.join(libcoap_dir, 'include', 'coap', 'coap.h.in')
176     config_h_file = os.path.join(libcoap_dir, 'include', 'coap', 'coap_config.h')
177     libcoap_env.PrependUnique(CPPPATH=['libcoap/include/coap'])
178     libcoap_env.AppendUnique(CPPDEFINES=['WITH_UPSTREAM_LIBCOAP'])
179
180     # Generate coap_config.h
181     target_arch = env.get('TARGET_ARCH')
182     lib_prefix = '' + str(libcoap_env.get('LIBPREFIX'))
183
184     if not os.path.isfile(config_h_file) and env.GetOption not in ('clean', 'help'):
185         conf = Configure(libcoap_env.Clone(LIBS=[]))
186
187         config_h_header = '''
188 /* ****************************************************************************
189  * coap_config.h - libcoap platform-specific configuration header.
190  *
191  * Auto-generated code for the %s %s platform. Do not edit.
192  *
193  *************************************************************************** */
194
195 #ifndef _COAP_CONFIG_H_
196 #define _COAP_CONFIG_H_
197
198 ''' % (str(target_os), str(target_arch))
199
200         config_h_footer = '''
201
202 /* Define to the full name of this package. */
203 #define PACKAGE_NAME "%s"
204
205 /* Define to the full name and version of this package. */
206 #define PACKAGE_STRING "%s"
207
208 #ifndef COAP_STATIC_INLINE
209 #  if defined(__cplusplus)
210 #    define COAP_STATIC_INLINE inline
211 #  else
212 #    ifdef _MSC_VER
213 #      define COAP_STATIC_INLINE static __inline
214 #    else
215 #      define COAP_STATIC_INLINE static inline
216 #    endif
217 #  endif
218 #endif
219
220 #endif // _COAP_CONFIG_H_
221
222 ''' % (str(lib_prefix + 'coap'), str(lib_prefix + 'coap ' + libcoap_version))
223
224         config_h_body = ''
225
226         if ((target_os == 'windows') and (libcoap_env.get('MSVC_UWP_APP') == '1')):
227             # Workaround for libcoap config [Investigation in IOT-2234]:
228             # libcoap builds its config file by trying to create a small program to see if an API is
229             # available. However, when building with store libraries on windows, it doesn't seem
230             # to find malloc or strnlen APIs. On Windows, those APIs are guaranteed to be there,
231             # therefore, create the libcoap config_h_body with what is needed and expected to be
232             # there.
233             config_h_body = '''
234 #define HAVE_ASSERT_H 1
235
236 #define HAVE_LIMITS_H 1
237
238 #define HAVE_STDIO_H 1
239
240 #define HAVE_SYS_TYPES_H 1
241
242 #define HAVE_TIME_H 1
243
244 #define HAVE_WINSOCK2_H 1
245
246 #define HAVE_WS2TCPIP_H 1
247
248 #define HAVE_MALLOC 1
249
250 #define HAVE_STRNLEN 1
251
252 #define ssize_t SSIZE_T
253
254 #define in_port_t uint16_t
255 '''
256         else:
257             cxx_headers = [
258                 'arpa/inet.h',
259                 'assert.h',
260                 'limits.h',
261                 'netinet/in.h',
262                 'stdio.h',
263                 'strings.h',
264                 'sys/select.h',
265                 'sys/socket.h',
266                 'sys/time.h',
267                 'sys/types.h',
268                 'sys/uio.h',
269                 'sys/unistd.h',
270                 'syslog.h',
271                 'time.h',
272                 'unistd.h',
273                 'winsock2.h',
274                 'ws2tcpip.h'
275             ]
276
277             cxx_functions = [
278                 'malloc',
279                 'snprintf',
280                 'strnlen',
281                 'vprintf',
282                 'fls',
283                 'flsll',
284             ]
285
286             def get_define_from_string(string_):
287                 string_converted = string_.replace("/", "_").replace(".", "_").upper()
288                 return "HAVE_" + string_converted
289
290             for header_file_name in cxx_headers:
291                 if conf.CheckCXXHeader(header_file_name):
292                     config_h_body += "#define %s 1\n\n" % get_define_from_string(
293                         header_file_name)
294
295             for function_name in cxx_functions:
296                 if conf.CheckFunc(function_name):
297                     config_h_body += "#define %s 1\n\n" % get_define_from_string(
298                         function_name)
299
300             if conf.CheckCXXHeader('windows.h'):
301                 config_h_body += "#define ssize_t SSIZE_T\n\n"
302                 config_h_body += "#define in_port_t uint16_t\n\n"
303
304             conf.Finish()
305
306         with open(config_h_file, "w") as configfile:
307             configfile.write(config_h_header + config_h_body + config_h_footer)
308
309     # Sanity check to ensure that the above block created the file.
310     if not os.path.exists(config_h_file) and not env.GetOption('clean'):
311         msg = "Error: coap_config.h file not created!"
312         Exit(msg)
313
314     # Generate coap.h from coap.h.in by substitution
315     pc_vars = {
316         '@PACKAGE_NAME@': lib_prefix + 'coap',
317         '@PACKAGE_STRING@': lib_prefix + 'coap-' + libcoap_version,
318         '@PACKAGE_URL@': libcoap_repo_url,
319         '@PACKAGE_BUGREPORT@': libcoap_repo_url + '/issues',
320         '@PACKAGE_VERSION@': libcoap_version
321     }
322     libcoap_env.Substfile(coap_h_pc_file, SUBST_DICT=pc_vars)
323
324     libcoap_src_root = 'libcoap/src/'
325     try:
326         libcoap_src = Glob(libcoap_src_root + '*.c',
327                            exclude=libcoap_src_root + 'coap_io_lwip.c')
328     except TypeError:   # very old SCons doesn't have 'exclude' in Glob
329         libcoap_src = Glob(libcoap_src_root + '*.c')
330         libcoap_src = [src for src in libcoap_src if 'coap_io_lwip.c' not in src.path]
331
332 # finally ready to build:
333 libcoap = libcoap_env.StaticLibrary('coap', libcoap_src, OBJPREFIX='libcoap_')
334
335 # set for use by other scripts:
336 if with_upstream_libcoap == '1':
337     env.AppendUnique(LIBCOAP_INC='#/extlibs/libcoap/libcoap/include')
338 else:
339     env.AppendUnique(LIBCOAP_INC='#/resource/csdk/connectivity/lib/libcoap-4.1.1/include')
340
341 libcoap_env.InstallTarget([libcoap], 'coap')
342 libcoap_env.UserInstallTargetLib(libcoap)