1#!/usr/bin/python
2#
3# Copyright (c) 2009-2021, Google LLC
4# All rights reserved.
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions are met:
8#     * Redistributions of source code must retain the above copyright
9#       notice, this list of conditions and the following disclaimer.
10#     * Redistributions in binary form must reproduce the above copyright
11#       notice, this list of conditions and the following disclaimer in the
12#       documentation and/or other materials provided with the distribution.
13#     * Neither the name of Google LLC nor the
14#       names of its contributors may be used to endorse or promote products
15#       derived from this software without specific prior written permission.
16#
17# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20# DISCLAIMED. IN NO EVENT SHALL Google LLC BE LIABLE FOR ANY
21# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
28"""A tool to convert {WORKSPACE, BUILD} -> CMakeLists.txt.
29
30This tool is very upb-specific at the moment, and should not be seen as a
31generic Bazel -> CMake converter.
32"""
33
34from __future__ import absolute_import
35from __future__ import division
36from __future__ import print_function
37
38import sys
39import textwrap
40import os
41
42def StripFirstChar(deps):
43  return [dep[1:] for dep in deps]
44
45def IsSourceFile(name):
46  return name.endswith(".c") or name.endswith(".cc")
47
48class BuildFileFunctions(object):
49  def __init__(self, converter):
50    self.converter = converter
51
52  def _add_deps(self, kwargs, keyword=""):
53    if "deps" not in kwargs:
54      return
55    self.converter.toplevel += "target_link_libraries(%s%s\n  %s)\n" % (
56        kwargs["name"],
57        keyword,
58        "\n  ".join(StripFirstChar(kwargs["deps"]))
59    )
60
61  def load(self, *args):
62    pass
63
64  def cc_library(self, **kwargs):
65    if kwargs["name"].endswith("amalgamation"):
66      return
67    if kwargs["name"] == "upbc_generator":
68      return
69    if kwargs["name"] == "lupb":
70      return
71    if "testonly" in kwargs:
72      return
73    files = kwargs.get("srcs", []) + kwargs.get("hdrs", [])
74    found_files = []
75    pregenerated_files = [
76        "CMakeLists.txt", "descriptor.upb.h", "descriptor.upb.c"
77    ]
78    for file in files:
79      if os.path.basename(file) in pregenerated_files:
80        found_files.append("../cmake/" + file)
81      else:
82        found_files.append("../" + file)
83
84    if list(filter(IsSourceFile, files)):
85      # Has sources, make this a normal library.
86      self.converter.toplevel += "add_library(%s\n  %s)\n" % (
87          kwargs["name"],
88          "\n  ".join(found_files)
89      )
90      self._add_deps(kwargs)
91    else:
92      # Header-only library, have to do a couple things differently.
93      # For some info, see:
94      #  http://mariobadr.com/creating-a-header-only-library-with-cmake.html
95      self.converter.toplevel += "add_library(%s INTERFACE)\n" % (
96          kwargs["name"]
97      )
98      self._add_deps(kwargs, " INTERFACE")
99
100  def cc_binary(self, **kwargs):
101    pass
102
103  def cc_test(self, **kwargs):
104    # Disable this until we properly support upb_proto_library().
105    # self.converter.toplevel += "add_executable(%s\n  %s)\n" % (
106    #     kwargs["name"],
107    #     "\n  ".join(kwargs["srcs"])
108    # )
109    # self.converter.toplevel += "add_test(NAME %s COMMAND %s)\n" % (
110    #     kwargs["name"],
111    #     kwargs["name"],
112    # )
113
114    # if "data" in kwargs:
115    #   for data_dep in kwargs["data"]:
116    #     self.converter.toplevel += textwrap.dedent("""\
117    #       add_custom_command(
118    #           TARGET %s POST_BUILD
119    #           COMMAND ${CMAKE_COMMAND} -E copy
120    #                   ${CMAKE_SOURCE_DIR}/%s
121    #                   ${CMAKE_CURRENT_BINARY_DIR}/%s)\n""" % (
122    #       kwargs["name"], data_dep, data_dep
123    #     ))
124
125    # self._add_deps(kwargs)
126    pass
127
128  def cc_fuzz_test(self, **kwargs):
129    pass
130
131  def pkg_files(self, **kwargs):
132    pass
133
134  def py_library(self, **kwargs):
135    pass
136
137  def py_binary(self, **kwargs):
138    pass
139
140  def lua_proto_library(self, **kwargs):
141    pass
142
143  def sh_test(self, **kwargs):
144    pass
145
146  def make_shell_script(self, **kwargs):
147    pass
148
149  def exports_files(self, files, **kwargs):
150    pass
151
152  def proto_library(self, **kwargs):
153    pass
154
155  def cc_proto_library(self, **kwargs):
156    pass
157
158  def staleness_test(self, **kwargs):
159    pass
160
161  def upb_amalgamation(self, **kwargs):
162    pass
163
164  def upb_proto_library(self, **kwargs):
165    pass
166
167  def upb_proto_library_copts(self, **kwargs):
168    pass
169
170  def upb_proto_reflection_library(self, **kwargs):
171    pass
172
173  def upb_proto_srcs(self, **kwargs):
174    pass
175
176  def genrule(self, **kwargs):
177    pass
178
179  def config_setting(self, **kwargs):
180    pass
181
182  def upb_fasttable_enabled(self, **kwargs):
183    pass
184
185  def select(self, arg_dict):
186    return []
187
188  def glob(self, *args, **kwargs):
189    return []
190
191  def licenses(self, *args):
192    pass
193
194  def filegroup(self, **kwargs):
195    pass
196
197  def map_dep(self, arg):
198    return arg
199
200  def package_group(self, **kwargs):
201    pass
202
203  def bool_flag(self, **kwargs):
204    pass
205
206  def bootstrap_upb_proto_library(self, **kwargs):
207    pass
208
209  def bootstrap_cc_library(self, **kwargs):
210    pass
211
212
213class WorkspaceFileFunctions(object):
214  def __init__(self, converter):
215    self.converter = converter
216
217  def load(self, *args, **kwargs):
218    pass
219
220  def workspace(self, **kwargs):
221    self.converter.prelude += "project(%s)\n" % (kwargs["name"])
222    self.converter.prelude += "set(CMAKE_C_STANDARD 99)\n"
223
224  def maybe(self, rule, **kwargs):
225    if kwargs["name"] == "utf8_range":
226      self.converter.utf8_range_commit = kwargs["commit"]
227    pass
228
229  def http_archive(self, **kwargs):
230    pass
231
232  def git_repository(self, **kwargs):
233    pass
234
235  def new_git_repository(self, **kwargs):
236    pass
237
238  def bazel_version_repository(self, **kwargs):
239    pass
240
241  def protobuf_deps(self):
242    pass
243
244  def utf8_range_deps(self):
245    pass
246
247  def pip_parse(self, **kwargs):
248    pass
249
250  def rules_fuzzing_dependencies(self):
251    pass
252
253  def rules_fuzzing_init(self):
254    pass
255
256  def rules_pkg_dependencies(self):
257    pass
258
259  def system_python(self, **kwargs):
260    pass
261
262  def register_system_python(self, **kwargs):
263    pass
264
265  def register_toolchains(self, toolchain):
266    pass
267
268  def python_source_archive(self, **kwargs):
269    pass
270
271  def python_nuget_package(self, **kwargs):
272    pass
273
274  def install_deps(self):
275    pass
276
277  def fuzzing_py_install_deps(self):
278    pass
279
280  def googletest_deps(self):
281    pass
282
283
284class Converter(object):
285  def __init__(self):
286    self.prelude = ""
287    self.toplevel = ""
288    self.if_lua = ""
289    self.utf8_range_commit = ""
290
291  def convert(self):
292    return self.template % {
293        "prelude": converter.prelude,
294        "toplevel": converter.toplevel,
295        "utf8_range_commit": converter.utf8_range_commit,
296    }
297
298  template = textwrap.dedent("""\
299    # This file was generated from BUILD using tools/make_cmakelists.py.
300
301    cmake_minimum_required(VERSION 3.1)
302
303    if(${CMAKE_VERSION} VERSION_LESS 3.12)
304        cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
305    else()
306        cmake_policy(VERSION 3.12)
307    endif()
308
309    cmake_minimum_required (VERSION 3.0)
310    cmake_policy(SET CMP0048 NEW)
311
312    %(prelude)s
313
314    # Prevent CMake from setting -rdynamic on Linux (!!).
315    SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "")
316    SET(CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS "")
317
318    # Set default build type.
319    if(NOT CMAKE_BUILD_TYPE)
320      message(STATUS "Setting build type to 'RelWithDebInfo' as none was specified.")
321      set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING
322          "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel."
323          FORCE)
324    endif()
325
326    # When using Ninja, compiler output won't be colorized without this.
327    include(CheckCXXCompilerFlag)
328    CHECK_CXX_COMPILER_FLAG(-fdiagnostics-color=always SUPPORTS_COLOR_ALWAYS)
329    if(SUPPORTS_COLOR_ALWAYS)
330      set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdiagnostics-color=always")
331    endif()
332
333    # Implement ASAN/UBSAN options
334    if(UPB_ENABLE_ASAN)
335      set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address")
336      set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address")
337      set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address")
338      set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fsanitize=address")
339    endif()
340
341    if(UPB_ENABLE_UBSAN)
342      set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined")
343      set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address")
344      set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address")
345      set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fsanitize=address")
346    endif()
347
348    include_directories(..)
349    include_directories(../cmake)
350    include_directories(${CMAKE_CURRENT_BINARY_DIR})
351
352    if(NOT TARGET utf8_range)
353      if(EXISTS ../external/utf8_range)
354        # utf8_range is already installed
355        include_directories(../external/utf8_range)
356      else()
357        include(FetchContent)
358        FetchContent_Declare(
359          utf8_range
360          GIT_REPOSITORY "https://github.com/protocolbuffers/utf8_range.git"
361          GIT_TAG "%(utf8_range_commit)s"
362        )
363        FetchContent_GetProperties(utf8_range)
364        if(NOT utf8_range_POPULATED)
365          FetchContent_Populate(utf8_range)
366          include_directories(${utf8_range_SOURCE_DIR})
367        endif()
368      endif()
369    endif()
370
371    if(APPLE)
372      set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -undefined dynamic_lookup -flat_namespace")
373    elseif(UNIX)
374      set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--build-id")
375    endif()
376
377    enable_testing()
378
379    %(toplevel)s
380
381  """)
382
383data = {}
384converter = Converter()
385
386def GetDict(obj):
387  ret = {}
388  ret["UPB_DEFAULT_COPTS"] = []  # HACK
389  ret["UPB_DEFAULT_CPPOPTS"] = []  # HACK
390  for k in dir(obj):
391    if not k.startswith("_"):
392      ret[k] = getattr(obj, k);
393  return ret
394
395globs = GetDict(converter)
396
397workspace_dict = GetDict(WorkspaceFileFunctions(converter))
398exec(open("bazel/workspace_deps.bzl").read(), workspace_dict)
399exec(open("WORKSPACE").read(), workspace_dict)
400exec(open("BUILD").read(), GetDict(BuildFileFunctions(converter)))
401
402with open(sys.argv[1], "w") as f:
403  f.write(converter.convert())
404