diff --git a/std/datetime.d b/std/datetime.d index 6b2d4f05c..eaca68275 100644 --- a/std/datetime.d +++ b/std/datetime.d @@ -28494,7 +28494,12 @@ public: } - version(Posix) + version(Android) + { + // Android concatenates all time zone data into a single file and stores it here. + enum defaultTZDatabaseDir = "/system/usr/share/zoneinfo/"; + } + else version(Posix) { /++ The default directory where the TZ Database files are. It's empty @@ -28549,13 +28554,22 @@ public: enforce(tzDatabaseDir.exists(), new DateTimeException(format("Directory %s does not exist.", tzDatabaseDir))); enforce(tzDatabaseDir.isDir, new DateTimeException(format("%s is not a directory.", tzDatabaseDir))); - const file = asNormalizedPath(chainPath(tzDatabaseDir, name)).to!string; + version(Android) + { + auto tzfileOffset = name in tzdataIndex(tzDatabaseDir); + enforce(tzfileOffset, new DateTimeException(format("The time zone %s is not listed.", name))); + string tzFilename = separate_index ? "zoneinfo.dat" : "tzdata"; + const file = asNormalizedPath(chainPath(tzDatabaseDir, tzFilename)).to!string; + } + else + const file = asNormalizedPath(chainPath(tzDatabaseDir, name)).to!string; enforce(file.exists(), new DateTimeException(format("File %s does not exist.", file))); enforce(file.isFile, new DateTimeException(format("%s is not a file.", file))); auto tzFile = File(file); - immutable gmtZone = file.representation().canFind("GMT"); + version(Android) tzFile.seek(*tzfileOffset); + immutable gmtZone = name.representation().canFind("GMT"); try { @@ -28739,8 +28753,16 @@ public: auto posixEnvStr = tzFile.readln().strip(); - _enforceValidTZFile(tzFile.readln().strip().empty); - _enforceValidTZFile(tzFile.eof); + version(Android) + { + // Android uses a single file for all timezone data, so the file + // doesn't end here. + } + else + { + _enforceValidTZFile(tzFile.readln().strip().empty); + _enforceValidTZFile(tzFile.eof); + } auto transitionTypes = new TransitionType*[](tempTTInfos.length); @@ -28889,20 +28911,31 @@ public: auto timezones = appender!(string[])(); - foreach(DirEntry dentry; dirEntries(tzDatabaseDir, SpanMode.depth)) + version(Android) { - if(dentry.isFile) + import std.algorithm : copy, filter; + tzdataIndex(tzDatabaseDir) + .byKey + .filter!(a => a.startsWith(subName)) + .copy(timezones); + } + else + { + foreach(DirEntry dentry; dirEntries(tzDatabaseDir, SpanMode.depth)) { - auto tzName = dentry.name[tzDatabaseDir.length .. $]; - - if(!tzName.extension().empty || - !tzName.startsWith(subName) || - tzName == "+VERSION") + if(dentry.isFile) { - continue; - } + auto tzName = dentry.name[tzDatabaseDir.length .. $]; - timezones.put(tzName); + if(!tzName.extension().empty || + !tzName.startsWith(subName) || + tzName == "+VERSION") + { + continue; + } + + timezones.put(tzName); + } } } @@ -28932,6 +28965,8 @@ public: foreach(tzName; tzNames) assertNotThrown!DateTimeException(testPTZSuccess(tzName)); + // No timezone directories on Android, just a single tzdata file + version(Android) {} else foreach(DirEntry dentry; dirEntries(defaultTZDatabaseDir, SpanMode.depth)) { if(dentry.isFile) @@ -29161,6 +29196,77 @@ private: _hasDST = hasDST; } + // Android concatenates the usual timezone directories into a single file, + // tzdata, along with an index to jump to each timezone's offset. In older + // versions of Android, the index was stored in a separate file, zoneinfo.idx, + // whereas now it's stored at the beginning of tzdata. + version(Android) + { + // Keep track of whether there's a separate index, zoneinfo.idx. Only + // check this after calling tzdataIndex, as it's initialized there. + static shared bool separate_index; + + // Extracts the name of each time zone and the offset where its data is + // located in the tzdata file from the index and caches it for later. + static const(uint[string]) tzdataIndex(string tzDir) + { + import std.concurrency : initOnce; + + static __gshared uint[string] _tzIndex; + + // _tzIndex is initialized once and then shared across all threads. + initOnce!_tzIndex( + { + import std.conv : to; + import std.format : format; + import std.path : asNormalizedPath, chainPath; + + enum indexEntrySize = 52; + const combinedFile = asNormalizedPath(chainPath(tzDir, "tzdata")).to!string; + const indexFile = asNormalizedPath(chainPath(tzDir, "zoneinfo.idx")).to!string; + File tzFile; + uint indexEntries, dataOffset; + uint[string] initIndex; + + // Check for the combined file tzdata, which stores the index + // and the time zone data together. + if(combinedFile.exists() && combinedFile.isFile) + { + tzFile = File(combinedFile); + _enforceValidTZFile(readVal!(char[])(tzFile, 6) == "tzdata"); + auto tzDataVersion = readVal!(char[])(tzFile, 6); + _enforceValidTZFile(tzDataVersion[5] == '\0'); + + uint indexOffset = readVal!uint(tzFile); + dataOffset = readVal!uint(tzFile); + readVal!uint(tzFile); + + indexEntries = (dataOffset - indexOffset)/indexEntrySize; + separate_index = false; + } + else if(indexFile.exists() && indexFile.isFile) + { + tzFile = File(indexFile); + indexEntries = to!(uint)(tzFile.size/indexEntrySize); + separate_index = true; + } + else + throw new DateTimeException(format("Both timezone files %s and %s do not exist.", + combinedFile, indexFile)); + + foreach(Unused; 0 .. indexEntries) { + string tzName = to!string(readVal!(char[])(tzFile, 40).ptr); + uint tzOffset = readVal!uint(tzFile); + readVal!(uint[])(tzFile, 2); + initIndex[tzName] = dataOffset + tzOffset; + } + initIndex.rehash; + return initIndex; + }()); + return _tzIndex; + } + } + /// List of times when the utc offset changes. immutable Transition[] _transitions; @@ -29655,7 +29761,10 @@ else version(Posix) import core.sys.posix.stdlib : setenv; import core.sys.posix.time : tzset; - auto value = asNormalizedPath(chainPath(PosixTimeZone.defaultTZDatabaseDir, tzDatabaseName)); + version(Android) + auto value = asNormalizedPath(tzDatabaseName); + else + auto value = asNormalizedPath(chainPath(PosixTimeZone.defaultTZDatabaseDir, tzDatabaseName)); setenv("TZ", value.tempCString(), 1); tzset(); } diff --git a/std/experimental/allocator/building_blocks/stats_collector.d b/std/experimental/allocator/building_blocks/stats_collector.d index 253bcc4c8..fda45aa0c 100644 --- a/std/experimental/allocator/building_blocks/stats_collector.d +++ b/std/experimental/allocator/building_blocks/stats_collector.d @@ -664,15 +664,12 @@ unittest alloc.reallocate(b, 20); alloc.deallocate(b); + import std.file : deleteme, remove; import std.stdio : File; import std.range : walkLength; - version(Posix) - auto f = "/tmp/dlang.std.experimental.allocator.stats_collector.txt"; - version(Windows) - { - import std.process: environment; - auto f = environment.get("temp") ~ r"\dlang.std.experimental.allocator.stats_collector.txt"; - } + + auto f = deleteme ~ "-dlang.std.experimental.allocator.stats_collector.txt"; + scope(exit) remove(f); Allocator.reportPerCallStatistics(File(f, "w")); alloc.reportStatistics(File(f, "a")); assert(File(f).byLine.walkLength == 22); diff --git a/std/file.d b/std/file.d index 420b4e418..e17b46f5b 100644 --- a/std/file.d +++ b/std/file.d @@ -3118,6 +3118,7 @@ private void copyImpl(const(char)[] f, const(char)[] t, const(FSChar)* fromz, co else version(Posix) { import core.stdc.stdio; + static import std.conv; immutable fd = core.sys.posix.fcntl.open(fromz, O_RDONLY); cenforce(fd != -1, f, fromz); @@ -3158,7 +3159,7 @@ private void copyImpl(const(char)[] f, const(char)[] t, const(FSChar)* fromz, co size -= toxfer; } if (preserve) - cenforce(fchmod(fdw, statbuf.st_mode) == 0, f, fromz); + cenforce(fchmod(fdw, std.conv.to!mode_t(statbuf.st_mode)) == 0, f, fromz); } cenforce(core.sys.posix.unistd.close(fdw) != -1, f, fromz); @@ -3902,6 +3903,11 @@ string tempDir() @trusted DWORD len = GetTempPathW(buf.length, buf.ptr); if (len) cache = toUTF8(buf[0 .. len]); } + else version(Android) + { + // Don't check for a global temporary directory as + // Android doesn't have one. + } else version(Posix) { import std.process : environment; diff --git a/std/format.d b/std/format.d index ef5200c74..40a907d1a 100644 --- a/std/format.d +++ b/std/format.d @@ -1793,9 +1793,7 @@ unittest formatTest( to!( const T)(5.5), "5.5" ); formatTest( to!(immutable T)(5.5), "5.5" ); - // bionic doesn't support lower-case string formatting of nan yet - version(CRuntime_Bionic) { formatTest( T.nan, "NaN" ); } - else { formatTest( T.nan, "nan" ); } + formatTest( T.nan, "nan" ); } } @@ -3722,13 +3720,6 @@ unittest || stream.data == "1.67 -0X1.47AE147AE147BP+0 nan", // MSVCRT 14+ (VS 2015) stream.data); } - else version (CRuntime_Bionic) - { - // bionic doesn't support hex formatting of floating point numbers - // or lower-case string formatting of nan yet, but it was committed - // recently (April 2014): - // https://code.google.com/p/android/issues/detail?id=64886 - } else { assert(stream.data == "1.67 -0X1.47AE147AE147BP+0 nan", @@ -3757,12 +3748,6 @@ unittest version (CRuntime_Microsoft) assert(stream.data == "0x1.51eb85p+0 0X1.B1EB86P+2" || stream.data == "0x1.51eb851eb851fp+0 0X1.B1EB860000000P+2"); // MSVCRT 14+ (VS 2015) - else version (CRuntime_Bionic) - { - // bionic doesn't support hex formatting of floating point numbers, - // but it was committed recently (April 2014): - // https://code.google.com/p/android/issues/detail?id=64886 - } else assert(stream.data == "0x1.51eb851eb851fp+0 0X1.B1EB86P+2"); stream.clear(); @@ -6118,13 +6103,6 @@ unittest else version (CRuntime_Microsoft) assert(s == "1.67 -0X1.47AE14P+0 nan" || s == "1.67 -0X1.47AE147AE147BP+0 nan", s); // MSVCRT 14+ (VS 2015) - else version (CRuntime_Bionic) - { - // bionic doesn't support hex formatting of floating point numbers - // or lower-case string formatting of nan yet, but it was committed - // recently (April 2014): - // https://code.google.com/p/android/issues/detail?id=64886 - } else assert(s == "1.67 -0X1.47AE147AE147BP+0 nan", s); diff --git a/std/process.d b/std/process.d index 20d3e4b4c..92ee21446 100644 --- a/std/process.d +++ b/std/process.d @@ -724,10 +724,10 @@ private bool isExecutable(in char[] path) @trusted nothrow @nogc //TODO: @safe version (Posix) unittest { import std.algorithm; - auto unamePath = searchPathFor("uname"); - assert (!unamePath.empty); - assert (unamePath[0] == '/'); - assert (unamePath.endsWith("uname")); + auto lsPath = searchPathFor("ls"); + assert (!lsPath.empty); + assert (lsPath[0] == '/'); + assert (lsPath.endsWith("ls")); auto unlikely = searchPathFor("lkmqwpoialhggyaofijadsohufoiqezm"); assert (unlikely is null, "Are you kidding me?"); } @@ -1474,7 +1474,12 @@ unittest // tryWait() and kill() TestScript prog = "while true; do sleep 1; done"; } auto pid = spawnProcess(prog.path); - Thread.sleep(dur!"seconds"(1)); + // Android appears to automatically kill sleeping processes very quickly, + // so shorten the wait before killing here. + version (Android) + Thread.sleep(dur!"msecs"(5)); + else + Thread.sleep(dur!"seconds"(1)); kill(pid); version (Windows) assert (wait(pid) == 1); else version (Posix) assert (wait(pid) == -SIGTERM); @@ -2147,6 +2152,10 @@ unittest "echo|set /p=%~1 echo|set /p=%~2 1>&2 exit 123"; + else version (Android) TestScript prog = + `echo -n $1 + echo -n $2 >&2 + exit 123`; else version (Posix) TestScript prog = `printf '%s' $1 printf '%s' $2 >&2 @@ -2239,17 +2248,15 @@ $(LREF nativeShell). */ @property string userShell() @safe { - version (Windows) return environment.get("COMSPEC", "cmd.exe"); - else version (Android) return environment.get("SHELL", "/system/bin/sh"); - else version (Posix) return environment.get("SHELL", "/bin/sh"); + version (Windows) return environment.get("COMSPEC", nativeShell); + else version (Posix) return environment.get("SHELL", nativeShell); } /** The platform-specific native shell path. -On Windows, this function returns $(D "cmd.exe"). - -On POSIX, $(D nativeShell) returns $(D "/bin/sh"). +This function returns $(D "cmd.exe") on Windows, $(D "/bin/sh") on POSIX, and +$(D "/system/bin/sh") on Android. */ @property string nativeShell() @safe @nogc pure nothrow { @@ -2344,8 +2351,7 @@ private struct TestScript else version (Posix) { auto ext = ""; - version(Android) auto firstLine = "#!" ~ userShell; - else auto firstLine = "#!/bin/sh"; + auto firstLine = "#!" ~ nativeShell; } path = uniqueTempPath()~ext; std.file.write(path, firstLine~std.ascii.newline~code~std.ascii.newline); diff --git a/std/socket.d b/std/socket.d index 94f5e321d..e1a49d97c 100644 --- a/std/socket.d +++ b/std/socket.d @@ -466,9 +466,11 @@ class Protocol } +// Skip this test on Android because getprotobyname/number are +// unimplemented in bionic. +version(CRuntime_Bionic) {} else unittest { - // getprotobyname,number are unimplemented in bionic softUnittest({ Protocol proto = new Protocol; assert(proto.getProtocolByType(ProtocolType.TCP));