Bladeren bron

LibTimeZone+Meta: Add plumbing for an IANA Time Zone Database generator

The IANA Time Zone Database contains data needed, at least, for various
JavaScript objects. This adds plumbing for a parser and code generator
for this data. The generated data will be made available by LibTimeZone,
much like how UCD and CLDR data is available through LibUnicode.
Timothy Flynn 3 jaren geleden
bovenliggende
commit
8669b25cea

+ 1 - 0
Meta/CMake/common_options.cmake

@@ -9,5 +9,6 @@ serenity_option(ENABLE_ALL_THE_DEBUG_MACROS OFF CACHE BOOL "Enable all debug mac
 serenity_option(ENABLE_ALL_DEBUG_FACILITIES OFF CACHE BOOL "Enable all noisy debug symbols and options. Not recommended for normal developer use")
 serenity_option(ENABLE_COMPILETIME_HEADER_CHECK OFF CACHE BOOL "Enable compiletime check that each library header compiles stand-alone")
 
+serenity_option(ENABLE_TIME_ZONE_DATABASE_DOWNLOAD ON CACHE BOOL "Enable download of the IANA Time Zone Database at build time")
 serenity_option(ENABLE_UNICODE_DATABASE_DOWNLOAD ON CACHE BOOL "Enable download of Unicode UCD and CLDR files at build time")
 serenity_option(INCLUDE_WASM_SPEC_TESTS OFF CACHE BOOL "Download and include the WebAssembly spec testsuite")

+ 88 - 0
Meta/CMake/time_zone_data.cmake

@@ -0,0 +1,88 @@
+include(${CMAKE_CURRENT_LIST_DIR}/utils.cmake)
+
+set(TZDB_PATH "${CMAKE_BINARY_DIR}/TZDB" CACHE PATH "Download location for TZDB files")
+
+set(TZDB_VERSION 2021e)
+set(TZDB_VERSION_FILE "${TZDB_PATH}/version.txt")
+
+set(TZDB_ZIP_URL "https://data.iana.org/time-zones/releases/tzdata${TZDB_VERSION}.tar.gz")
+set(TZDB_ZIP_PATH "${TZDB_PATH}/tzdb.tar.gz")
+
+set(TZDB_AFRICA_SOURCE africa)
+set(TZDB_AFRICA_PATH "${TZDB_PATH}/${TZDB_AFRICA_SOURCE}")
+
+set(TZDB_ANTARCTICA_SOURCE antarctica)
+set(TZDB_ANTARCTICA_PATH "${TZDB_PATH}/${TZDB_ANTARCTICA_SOURCE}")
+
+set(TZDB_ASIA_SOURCE asia)
+set(TZDB_ASIA_PATH "${TZDB_PATH}/${TZDB_ASIA_SOURCE}")
+
+set(TZDB_AUSTRALASIA_SOURCE australasia)
+set(TZDB_AUSTRALASIA_PATH "${TZDB_PATH}/${TZDB_AUSTRALASIA_SOURCE}")
+
+set(TZDB_ETCETERA_SOURCE etcetera)
+set(TZDB_ETCETERA_PATH "${TZDB_PATH}/${TZDB_ETCETERA_SOURCE}")
+
+set(TZDB_EUROPE_SOURCE europe)
+set(TZDB_EUROPE_PATH "${TZDB_PATH}/${TZDB_EUROPE_SOURCE}")
+
+set(TZDB_NORTH_AMERICA_SOURCE northamerica)
+set(TZDB_NORTH_AMERICA_PATH "${TZDB_PATH}/${TZDB_NORTH_AMERICA_SOURCE}")
+
+set(TZDB_SOUTH_AMERICA_SOURCE southamerica)
+set(TZDB_SOUTH_AMERICA_PATH "${TZDB_PATH}/${TZDB_SOUTH_AMERICA_SOURCE}")
+
+function(extract_tzdb_file source path)
+    if(EXISTS "${TZDB_ZIP_PATH}" AND NOT EXISTS "${path}")
+        message(STATUS "Extracting TZDB ${source} from ${TZDB_ZIP_PATH}...")
+        execute_process(COMMAND tar -C "${TZDB_PATH}" -xf "${TZDB_ZIP_PATH}" "${source}" RESULT_VARIABLE tar_result)
+        if (NOT tar_result EQUAL 0)
+            message(FATAL_ERROR "Failed to unzip ${source} from ${TZDB_ZIP_PATH} with status ${tar_result}")
+        endif()
+    endif()
+endfunction()
+
+if (ENABLE_TIME_ZONE_DATABASE_DOWNLOAD)
+    remove_path_if_version_changed("${TZDB_VERSION}" "${TZDB_VERSION_FILE}" "${TZDB_PATH}")
+
+    if (NOT EXISTS "${TZDB_ZIP_PATH}")
+        message(STATUS "Downloading time zone database from ${TZDB_ZIP_URL}...")
+        file(DOWNLOAD "${TZDB_ZIP_URL}" "${TZDB_ZIP_PATH}" INACTIVITY_TIMEOUT 10)
+    endif()
+
+    extract_tzdb_file("${TZDB_AFRICA_SOURCE}" "${TZDB_AFRICA_PATH}")
+    extract_tzdb_file("${TZDB_ANTARCTICA_SOURCE}" "${TZDB_ANTARCTICA_PATH}")
+    extract_tzdb_file("${TZDB_ASIA_SOURCE}" "${TZDB_ASIA_PATH}")
+    extract_tzdb_file("${TZDB_AUSTRALASIA_SOURCE}" "${TZDB_AUSTRALASIA_PATH}")
+    extract_tzdb_file("${TZDB_ETCETERA_SOURCE}" "${TZDB_ETCETERA_PATH}")
+    extract_tzdb_file("${TZDB_EUROPE_SOURCE}" "${TZDB_EUROPE_PATH}")
+    extract_tzdb_file("${TZDB_NORTH_AMERICA_SOURCE}" "${TZDB_NORTH_AMERICA_PATH}")
+    extract_tzdb_file("${TZDB_SOUTH_AMERICA_SOURCE}" "${TZDB_SOUTH_AMERICA_PATH}")
+
+    set(TIME_ZONE_DATA_HEADER LibTimeZone/TimeZoneData.h)
+    set(TIME_ZONE_DATA_IMPLEMENTATION LibTimeZone/TimeZoneData.cpp)
+
+    set(TIME_ZONE_META_TARGET_PREFIX LibTimeZone_)
+
+    if (CMAKE_CURRENT_BINARY_DIR MATCHES ".*/LibTimeZone") # Serenity build.
+        set(TIME_ZONE_DATA_HEADER TimeZoneData.h)
+        set(TIME_ZONE_DATA_IMPLEMENTATION TimeZoneData.cpp)
+
+        set(TIME_ZONE_META_TARGET_PREFIX "")
+    endif()
+
+    invoke_generator(
+        "TimeZoneData"
+        Lagom::GenerateTimeZoneData
+        "${TZDB_VERSION_FILE}"
+        "${TIME_ZONE_META_TARGET_PREFIX}"
+        "${TIME_ZONE_DATA_HEADER}"
+        "${TIME_ZONE_DATA_IMPLEMENTATION}"
+        arguments "${TZDB_AFRICA_PATH}" "${TZDB_ANTARCTICA_PATH}" "${TZDB_ASIA_PATH}" "${TZDB_AUSTRALASIA_PATH}" "${TZDB_ETCETERA_PATH}" "${TZDB_EUROPE_PATH}" "${TZDB_NORTH_AMERICA_PATH}" "${TZDB_SOUTH_AMERICA_PATH}"
+    )
+
+    set(TIME_ZONE_DATA_SOURCES
+        ${TIME_ZONE_DATA_HEADER}
+        ${TIME_ZONE_DATA_IMPLEMENTATION}
+    )
+endif()

+ 18 - 6
Meta/Lagom/CMakeLists.txt

@@ -216,6 +216,11 @@ function(lagom_test source)
     )
 endfunction()
 
+if (NOT TARGET all_generated)
+    # Meta target to run all code-gen steps in the build.
+    add_custom_target(all_generated)
+endif()
+
 # AK/Core
 # Note: AK is included in LagomCore for the host build instead of LibC per the target build
 file(GLOB AK_SOURCES CONFIGURE_DEPENDS "../../AK/*.cpp")
@@ -240,6 +245,19 @@ if (APPLE)
     target_link_options(LagomMain PRIVATE -undefined dynamic_lookup)
 endif()
 
+# TimeZone
+# This is needed even if Lagom is not enabled because it is depended upon by code generators.
+if (NOT ENABLE_OSS_FUZZ AND NOT ENABLE_FUZZER_SANITIZER)
+    include(time_zone_data)
+else()
+    set(ENABLE_TIME_ZONE_DATABASE_DOWNLOAD OFF)
+endif()
+file(GLOB LIBTIMEZONE_SOURCES CONFIGURE_DEPENDS "../../Userland/Libraries/LibTimeZone/*.cpp")
+lagom_lib(TimeZone timezone
+    SOURCES ${LIBTIMEZONE_SOURCES} ${TIME_ZONE_DATA_SOURCES}
+)
+target_compile_definitions(LagomTimeZone PRIVATE ENABLE_TIME_ZONE_DATA=$<BOOL:${ENABLE_TIME_ZONE_DATABASE_DOWNLOAD}>)
+
 # Manually install AK headers
 install(
     DIRECTORY "${SERENITY_PROJECT_ROOT}/AK"
@@ -255,12 +273,6 @@ if (NOT ENABLE_OSS_FUZZ AND NOT ENABLE_FUZZER_SANITIZER)
 endif()
 
 if (BUILD_LAGOM)
-
-    if (NOT TARGET all_generated)
-        # Meta target to run all code-gen steps in the build.
-        add_custom_target(all_generated)
-    endif()
-
     # Lagom Libraries
 
     # Archive

+ 1 - 0
Meta/Lagom/Tools/CodeGenerators/CMakeLists.txt

@@ -1,4 +1,5 @@
 add_subdirectory(IPCCompiler)
+add_subdirectory(LibTimeZone)
 add_subdirectory(LibUnicode)
 add_subdirectory(LibWeb)
 add_subdirectory(StateMachineGenerator)

+ 1 - 0
Meta/Lagom/Tools/CodeGenerators/LibTimeZone/CMakeLists.txt

@@ -0,0 +1 @@
+lagom_tool(GenerateTimeZoneData SOURCES GenerateTimeZoneData.cpp LIBS LagomMain)

+ 66 - 0
Meta/Lagom/Tools/CodeGenerators/LibTimeZone/GenerateTimeZoneData.cpp

@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2022, Tim Flynn <trflynn89@pm.me>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <AK/SourceGenerator.h>
+#include <AK/String.h>
+#include <AK/StringBuilder.h>
+#include <AK/Vector.h>
+#include <LibCore/ArgsParser.h>
+#include <LibCore/File.h>
+
+static void generate_time_zone_data_header(Core::File& file)
+{
+    StringBuilder builder;
+    SourceGenerator generator { builder };
+
+    generator.append(R"~~~(
+#pragma once
+)~~~");
+
+    VERIFY(file.write(generator.as_string_view()));
+}
+
+static void generate_time_zone_data_implementation(Core::File& file)
+{
+    StringBuilder builder;
+    SourceGenerator generator { builder };
+
+    generator.append(R"~~~(
+#include <LibTimeZone/TimeZoneData.h>
+)~~~");
+
+    VERIFY(file.write(generator.as_string_view()));
+}
+
+ErrorOr<int> serenity_main(Main::Arguments arguments)
+{
+    StringView generated_header_path;
+    StringView generated_implementation_path;
+    Vector<StringView> time_zone_paths;
+
+    Core::ArgsParser args_parser;
+    args_parser.add_option(generated_header_path, "Path to the time zone data header file to generate", "generated-header-path", 'h', "generated-header-path");
+    args_parser.add_option(generated_implementation_path, "Path to the time zone data implementation file to generate", "generated-implementation-path", 'c', "generated-implementation-path");
+    args_parser.add_positional_argument(time_zone_paths, "Paths to the time zone database files", "time-zone-paths");
+    args_parser.parse(arguments);
+
+    auto open_file = [&](StringView path) -> ErrorOr<NonnullRefPtr<Core::File>> {
+        if (path.is_empty()) {
+            args_parser.print_usage(stderr, arguments.argv[0]);
+            return Error::from_string_literal("Must provide all command line options"sv);
+        }
+
+        return Core::File::open(path, Core::OpenMode::ReadWrite);
+    };
+
+    auto generated_header_file = TRY(open_file(generated_header_path));
+    auto generated_implementation_file = TRY(open_file(generated_implementation_path));
+
+    generate_time_zone_data_header(generated_header_file);
+    generate_time_zone_data_implementation(generated_implementation_file);
+
+    return 0;
+}

+ 1 - 0
Userland/Libraries/CMakeLists.txt

@@ -46,6 +46,7 @@ add_subdirectory(LibSystem)
 add_subdirectory(LibTest)
 add_subdirectory(LibTextCodec)
 add_subdirectory(LibThreading)
+add_subdirectory(LibTimeZone)
 add_subdirectory(LibTLS)
 add_subdirectory(LibUnicode)
 add_subdirectory(LibUSBDB)

+ 8 - 0
Userland/Libraries/LibTimeZone/CMakeLists.txt

@@ -0,0 +1,8 @@
+include(${SerenityOS_SOURCE_DIR}/Meta/CMake/time_zone_data.cmake)
+
+if (DEFINED TIME_ZONE_DATA_SOURCES)
+    set(SOURCES ${TIME_ZONE_DATA_SOURCES})
+    serenity_lib(LibTimeZoneData timezonedata)
+    target_compile_options(LibTimeZoneData PRIVATE -g0 -Os)
+    target_link_libraries(LibTimeZoneData LibCore)
+endif()