Skip to content

Commit

Permalink
ARROW-8795: [C++] Limited iOS support
Browse files Browse the repository at this point in the history
Per suggestion from [JIRA](https://issues.apache.org/jira/browse/ARROW-8795), opening up a pull request to potentially get the ball rolling on having iOS as one of the supported platforms.

Per writeup in [this doc](https://github.com/UnfoldedInc/deck.gl-native-dependencies/blob/master/docs/iOS-BUILD.md#arrow-v0170), there are a few issues with cmake's Xcode generator that require a few workarounds even when using their most recent versions.

The writeup also describes issues that we've encountered building the library, as well as issues that were not resolved as they use optional features we are currently not making use of.

Closes apache#7189 from ilijapuaca/feature/ios-support

Authored-by: Ilija Puaca <[email protected]>
Signed-off-by: Antoine Pitrou <[email protected]>
  • Loading branch information
ilijapuaca authored and pitrou committed Jun 4, 2020
1 parent c7baa7c commit 43cb8d4
Show file tree
Hide file tree
Showing 5 changed files with 352 additions and 1 deletion.
1 change: 1 addition & 0 deletions LICENSE.txt
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,7 @@ limitations under the License.
--------------------------------------------------------------------------------
The files cpp/src/arrow/vendored/datetime/date.h, cpp/src/arrow/vendored/datetime/tz.h,
cpp/src/arrow/vendored/datetime/tz_private.h, cpp/src/arrow/vendored/datetime/ios.h,
cpp/src/arrow/vendored/datetime/ios.mm,
cpp/src/arrow/vendored/datetime/tz.cpp are adapted from
Howard Hinnant's date library (https://github.com/HowardHinnant/date)
It is licensed under MIT license.
Expand Down
5 changes: 5 additions & 0 deletions cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,11 @@ if(ARROW_ORC)
set(ARROW_WITH_ZSTD ON)
endif()

# datetime code used by iOS requires zlib support
if(IOS)
set(ARROW_WITH_ZLIB ON)
endif()

if(NOT ARROW_BUILD_TESTS)
set(NO_TESTS 1)
else()
Expand Down
3 changes: 2 additions & 1 deletion cpp/cmake_modules/BuildUtils.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,8 @@ function(ADD_ARROW_LIB LIB_NAME)
target_include_directories(${LIB_NAME}_shared PRIVATE ${ARG_PRIVATE_INCLUDES})
endif()

if(APPLE AND NOT DEFINED $ENV{EMSCRIPTEN})
# On iOS, specifying -undefined conflicts with enabling bitcode
if(APPLE AND NOT IOS AND NOT DEFINED $ENV{EMSCRIPTEN})
# On OS X, you can avoid linking at library load time and instead
# expecting that the symbols have been loaded separately. This happens
# with libpython* where there can be conflicts between system Python and
Expand Down
4 changes: 4 additions & 0 deletions cpp/src/arrow/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,10 @@ set(ARROW_SRCS
vendored/double-conversion/diy-fp.cc
vendored/double-conversion/strtod.cc)

if(APPLE)
list(APPEND ARROW_SRCS vendored/datetime/ios.mm)
endif()

set(ARROW_C_SRCS
vendored/musl/strptime.c
vendored/uriparser/UriCommon.c
Expand Down
340 changes: 340 additions & 0 deletions cpp/src/arrow/vendored/datetime/ios.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,340 @@
//
// The MIT License (MIT)
//
// Copyright (c) 2016 Alexander Kormanovsky
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//

#include "ios.h"

#if TARGET_OS_IPHONE

#include <Foundation/Foundation.h>

#include <fstream>
#include <zlib.h>
#include <sys/stat.h>

#ifndef TAR_DEBUG
# define TAR_DEBUG 0
#endif

#define INTERNAL_DIR "Library"
#define TZDATA_DIR "tzdata"
#define TARGZ_EXTENSION "tar.gz"

#define TAR_BLOCK_SIZE 512
#define TAR_TYPE_POSITION 156
#define TAR_NAME_POSITION 0
#define TAR_NAME_SIZE 100
#define TAR_SIZE_POSITION 124
#define TAR_SIZE_SIZE 12

namespace arrow_vendored
{
namespace date
{
namespace iOSUtils
{

struct TarInfo
{
char objType;
std::string objName;
size_t realContentSize; // writable size without padding zeroes
size_t blocksContentSize; // adjusted size to 512 bytes blocks
bool success;
};

std::string convertCFStringRefPathToCStringPath(CFStringRef ref);
bool extractTzdata(CFURLRef homeUrl, CFURLRef archiveUrl, std::string destPath);
TarInfo getTarObjectInfo(std::ifstream &readStream);
std::string getTarObject(std::ifstream &readStream, int64_t size);
bool writeFile(const std::string &tzdataPath, const std::string &fileName,
const std::string &data, size_t realContentSize);

std::string
get_current_timezone()
{
CFTimeZoneRef tzRef = CFTimeZoneCopySystem();
CFStringRef tzNameRef = CFTimeZoneGetName(tzRef);
CFIndex bufferSize = CFStringGetLength(tzNameRef) + 1;
char buffer[bufferSize];

if (CFStringGetCString(tzNameRef, buffer, bufferSize, kCFStringEncodingUTF8))
{
CFRelease(tzRef);
return std::string(buffer);
}

CFRelease(tzRef);

return "";
}

std::string
get_tzdata_path()
{
CFURLRef homeUrlRef = CFCopyHomeDirectoryURL();
CFStringRef homePath = CFURLCopyPath(homeUrlRef);
std::string path(std::string(convertCFStringRefPathToCStringPath(homePath)) +
INTERNAL_DIR + "/" + TZDATA_DIR);
std::string result_path(std::string(convertCFStringRefPathToCStringPath(homePath)) +
INTERNAL_DIR);

if (access(path.c_str(), F_OK) == 0)
{
#if TAR_DEBUG
printf("tzdata dir exists\n");
#endif
CFRelease(homeUrlRef);
CFRelease(homePath);

return result_path;
}

CFBundleRef mainBundle = CFBundleGetMainBundle();
CFArrayRef paths = CFBundleCopyResourceURLsOfType(mainBundle, CFSTR(TARGZ_EXTENSION),
NULL);

if (CFArrayGetCount(paths) != 0)
{
// get archive path, assume there is no other tar.gz in bundle
CFURLRef archiveUrl = static_cast<CFURLRef>(CFArrayGetValueAtIndex(paths, 0));
CFStringRef archiveName = CFURLCopyPath(archiveUrl);
archiveUrl = CFBundleCopyResourceURL(mainBundle, archiveName, NULL, NULL);

extractTzdata(homeUrlRef, archiveUrl, path);

CFRelease(archiveUrl);
CFRelease(archiveName);
}

CFRelease(homeUrlRef);
CFRelease(homePath);
CFRelease(paths);

return result_path;
}

std::string
convertCFStringRefPathToCStringPath(CFStringRef ref)
{
CFIndex bufferSize = CFStringGetMaximumSizeOfFileSystemRepresentation(ref);
char *buffer = new char[bufferSize];
CFStringGetFileSystemRepresentation(ref, buffer, bufferSize);
auto result = std::string(buffer);
delete[] buffer;
return result;
}

bool
extractTzdata(CFURLRef homeUrl, CFURLRef archiveUrl, std::string destPath)
{
std::string TAR_TMP_PATH = "/tmp.tar";

CFStringRef homeStringRef = CFURLCopyPath(homeUrl);
auto homePath = convertCFStringRefPathToCStringPath(homeStringRef);
CFRelease(homeStringRef);

CFStringRef archiveStringRef = CFURLCopyPath(archiveUrl);
auto archivePath = convertCFStringRefPathToCStringPath(archiveStringRef);
CFRelease(archiveStringRef);

// create Library path
auto libraryPath = homePath + INTERNAL_DIR;

// create tzdata path
auto tzdataPath = libraryPath + "/" + TZDATA_DIR;

// -- replace %20 with " "
const std::string search = "%20";
const std::string replacement = " ";
size_t pos = 0;

while ((pos = archivePath.find(search, pos)) != std::string::npos) {
archivePath.replace(pos, search.length(), replacement);
pos += replacement.length();
}

gzFile tarFile = gzopen(archivePath.c_str(), "rb");

// create tar unpacking path
auto tarPath = libraryPath + TAR_TMP_PATH;

// create tzdata directory
mkdir(destPath.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);

// ======= extract tar ========

std::ofstream os(tarPath.c_str(), std::ofstream::out | std::ofstream::app);
unsigned int bufferLength = 1024 * 256; // 256Kb
unsigned char *buffer = (unsigned char *)malloc(bufferLength);
bool success = true;

while (true)
{
int readBytes = gzread(tarFile, buffer, bufferLength);

if (readBytes > 0)
{
os.write((char *) &buffer[0], readBytes);
}
else
if (readBytes == 0)
{
break;
}
else
if (readBytes == -1)
{
printf("decompression failed\n");
success = false;
break;
}
else
{
printf("unexpected zlib state\n");
success = false;
break;
}
}

os.close();
free(buffer);
gzclose(tarFile);

if (!success)
{
remove(tarPath.c_str());
return false;
}

// ======== extract files =========

uint64_t location = 0; // Position in the file

// get file size
struct stat stat_buf;
int res = stat(tarPath.c_str(), &stat_buf);
if (res != 0)
{
printf("error file size\n");
remove(tarPath.c_str());
return false;
}
int64_t tarSize = stat_buf.st_size;

// create read stream
std::ifstream is(tarPath.c_str(), std::ifstream::in | std::ifstream::binary);

// process files
while (location < tarSize)
{
TarInfo info = getTarObjectInfo(is);

if (!info.success || info.realContentSize == 0)
{
break; // something wrong or all files are read
}

switch (info.objType)
{
case '0': // file
case '\0': //
{
std::string obj = getTarObject(is, info.blocksContentSize);
#if TAR_DEBUG
size += info.realContentSize;
printf("#%i %s file size %lld written total %ld from %lld\n", ++count,
info.objName.c_str(), info.realContentSize, size, tarSize);
#endif
writeFile(tzdataPath, info.objName, obj, info.realContentSize);
location += info.blocksContentSize;

break;
}
}
}

remove(tarPath.c_str());

return true;
}

TarInfo
getTarObjectInfo(std::ifstream &readStream)
{
int64_t length = TAR_BLOCK_SIZE;
char buffer[length];
char type;
char name[TAR_NAME_SIZE + 1];
char sizeBuf[TAR_SIZE_SIZE + 1];

readStream.read(buffer, length);

memcpy(&type, &buffer[TAR_TYPE_POSITION], 1);

memset(&name, '\0', TAR_NAME_SIZE + 1);
memcpy(&name, &buffer[TAR_NAME_POSITION], TAR_NAME_SIZE);

memset(&sizeBuf, '\0', TAR_SIZE_SIZE + 1);
memcpy(&sizeBuf, &buffer[TAR_SIZE_POSITION], TAR_SIZE_SIZE);
size_t realSize = strtol(sizeBuf, NULL, 8);
size_t blocksSize = realSize + (TAR_BLOCK_SIZE - (realSize % TAR_BLOCK_SIZE));

return {type, std::string(name), realSize, blocksSize, true};
}

std::string
getTarObject(std::ifstream &readStream, int64_t size)
{
char buffer[size];
readStream.read(buffer, size);
return std::string(buffer);
}

bool
writeFile(const std::string &tzdataPath, const std::string &fileName, const std::string &data,
size_t realContentSize)
{
std::ofstream os(tzdataPath + "/" + fileName, std::ofstream::out | std::ofstream::binary);

if (!os) {
return false;
}

// trim empty space
char trimmedData[realContentSize + 1];
memset(&trimmedData, '\0', realContentSize);
memcpy(&trimmedData, data.c_str(), realContentSize);

// write
os.write(trimmedData, realContentSize);
os.close();

return true;
}

} // namespace iOSUtils
} // namespace date
} // namespace arrow_vendored

#endif // TARGET_OS_IPHONE

0 comments on commit 43cb8d4

Please sign in to comment.