From 41a5c9212eae3e9c2a4b5411fc0b0e78e4b1c927 Mon Sep 17 00:00:00 2001 From: "ager@chromium.org" Date: Fri, 21 Nov 2008 10:06:29 +0000 Subject: [PATCH] Apply patch from Alexander Botero-Lowry that adds FreeBSD platform support. Review URL: http://codereview.chromium.org/11347 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@815 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- AUTHORS | 1 + SConstruct | 10 +- src/SConscript | 9 +- src/dtoa-config.c | 14 +- src/platform-freebsd.cc | 698 ++++++++++++++++++++++++++++++++++++++++++++++++ tools/utils.py | 4 +- 6 files changed, 723 insertions(+), 13 deletions(-) create mode 100644 src/platform-freebsd.cc diff --git a/AUTHORS b/AUTHORS index 596cc58..b95551e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -11,3 +11,4 @@ Jay Freeman Daniel James Paolo Giarrusso Daniel Andersson +Alexander Botero-Lowry diff --git a/SConstruct b/SConstruct index 9dd9268..a6f7738 100644 --- a/SConstruct +++ b/SConstruct @@ -54,6 +54,9 @@ LIBRARY_FLAGS = { 'mode:release': { 'CCFLAGS': ['-O3', '-fomit-frame-pointer'] }, + 'os:freebsd': { + 'LIBS': ['execinfo'] + }, 'wordsize:64': { 'CCFLAGS': ['-m32'], 'LINKFLAGS': ['-m32'] @@ -186,6 +189,9 @@ SAMPLE_FLAGS = { 'LIBS': ['pthread'], 'LIBPATH': ['.'] }, + 'os:freebsd': { + 'LIBS': ['execinfo'] + }, 'wordsize:64': { 'CCFLAGS': ['-m32'], 'LINKFLAGS': ['-m32'] @@ -264,7 +270,7 @@ SIMPLE_OPTIONS = { 'help': 'the toolchain to use' }, 'os': { - 'values': ['linux', 'macos', 'win32'], + 'values': ['freebsd', 'linux', 'macos', 'win32'], 'default': OS_GUESS, 'help': 'the os to build for' }, @@ -541,7 +547,7 @@ def Build(): env.Alias('cctests', cctests) env.Alias('sample', samples) env.Alias('d8', d8s) - + if env['sample']: env.Default('sample') else: diff --git a/src/SConscript b/src/SConscript index c1a6433..8226a8d 100644 --- a/src/SConscript +++ b/src/SConscript @@ -55,10 +55,11 @@ SOURCES = { 'cpu-ia32.cc', 'disasm-ia32.cc', 'frames-ia32.cc', 'ic-ia32.cc', 'macro-assembler-ia32.cc', 'stub-cache-ia32.cc'], 'simulator:arm': ['simulator-arm.cc'], - 'os:linux': ['platform-linux.cc'], - 'os:macos': ['platform-macos.cc'], - 'os:nullos': ['platform-nullos.cc'], - 'os:win32': ['platform-win32.cc'], + 'os:freebsd': ['platform-freebsd.cc'], + 'os:linux': ['platform-linux.cc'], + 'os:macos': ['platform-macos.cc'], + 'os:nullos': ['platform-nullos.cc'], + 'os:win32': ['platform-win32.cc'], 'mode:release': [], 'mode:debug': ['objects-debug.cc', 'prettyprinter.cc'] } diff --git a/src/dtoa-config.c b/src/dtoa-config.c index cb73c17..bb610a9 100644 --- a/src/dtoa-config.c +++ b/src/dtoa-config.c @@ -37,7 +37,8 @@ * subtly wrong. */ -#if !(defined(__APPLE__) && defined(__MACH__)) && !defined(WIN32) +#if !(defined(__APPLE__) && defined(__MACH__)) && \ + !defined(WIN32) && !defined(__FreeBSD__) #include #endif #include @@ -46,17 +47,18 @@ /* The floating point word order on ARM is big endian when floating point * emulation is used, even if the byte order is little endian */ #if !(defined(__APPLE__) && defined(__MACH__)) && !defined(WIN32) && \ - __FLOAT_WORD_ORDER == __BIG_ENDIAN + !defined(__FreeBSD__) && __FLOAT_WORD_ORDER == __BIG_ENDIAN #define IEEE_MC68k #else #define IEEE_8087 #endif #define __MATH_H__ -#if defined(__APPLE__) && defined(__MACH__) -/* stdlib.h on Apple's 10.5 and later SDKs will mangle the name of strtod. - * If it's included after strtod is redefined as gay_strtod, it will mangle - * the name of gay_strtod, which is unwanted. */ +#if defined(__APPLE__) && defined(__MACH__) || defined(__FreeBSD__) +/* stdlib.h on FreeBSD and Apple's 10.5 and later SDKs will mangle the + * name of strtod. If it's included after strtod is redefined as + * gay_strtod, it will mangle the name of gay_strtod, which is + * unwanted. */ #include #endif /* Make sure we use the David M. Gay version of strtod(). On Linux, we diff --git a/src/platform-freebsd.cc b/src/platform-freebsd.cc new file mode 100644 index 0000000..75140c9 --- /dev/null +++ b/src/platform-freebsd.cc @@ -0,0 +1,698 @@ +// Copyright 2006-2008 the V8 project authors. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Platform specific code for FreeBSD goes here + +#include +#include +#include +#include +#include +#include +#include + +#include // mmap & munmap +#include // mmap & munmap +#include // open +#include // open +#include // getpagesize +#include // backtrace, backtrace_symbols +#include // index +#include +#include +#include + +#undef MAP_TYPE + +#include "v8.h" + +#include "platform.h" + + +namespace v8 { namespace internal { + +// 0 is never a valid thread id on FreeBSD since tids and pids share a +// name space and pid 0 is used to kill the group (see man 2 kill). +static const pthread_t kNoThread = (pthread_t) 0; + + +double ceiling(double x) { + // Correct as on OS X + if (-1.0 < x && x < 0.0) { + return -0.0; + } else { + return ceil(x); + } +} + + +void OS::Setup() { + // Seed the random number generator. + // Convert the current time to a 64-bit integer first, before converting it + // to an unsigned. Going directly can cause an overflow and the seed to be + // set to all ones. The seed will be identical for different instances that + // call this setup code within the same millisecond. + uint64_t seed = static_cast(TimeCurrentMillis()); + srandom(static_cast(seed)); +} + + +int OS::GetUserTime(uint32_t* secs, uint32_t* usecs) { + struct rusage usage; + + if (getrusage(RUSAGE_SELF, &usage) < 0) return -1; + *secs = usage.ru_utime.tv_sec; + *usecs = usage.ru_utime.tv_usec; + return 0; +} + + +double OS::TimeCurrentMillis() { + struct timeval tv; + if (gettimeofday(&tv, NULL) < 0) return 0.0; + return (static_cast(tv.tv_sec) * 1000) + + (static_cast(tv.tv_usec) / 1000); +} + + +int64_t OS::Ticks() { + // FreeBSD's gettimeofday has microsecond resolution. + struct timeval tv; + if (gettimeofday(&tv, NULL) < 0) + return 0; + return (static_cast(tv.tv_sec) * 1000000) + tv.tv_usec; +} + + +char* OS::LocalTimezone(double time) { + time_t tv = static_cast(floor(time/msPerSecond)); + struct tm* t = localtime(&tv); + return const_cast(t->tm_zone); +} + + +double OS::DaylightSavingsOffset(double time) { + time_t tv = static_cast(floor(time/msPerSecond)); + struct tm* t = localtime(&tv); + return t->tm_isdst > 0 ? 3600 * msPerSecond : 0; +} + + +double OS::LocalTimeOffset() { + time_t tv = time(NULL); + struct tm* t = localtime(&tv); + // tm_gmtoff includes any daylight savings offset, so subtract it. + return static_cast(t->tm_gmtoff * msPerSecond - + (t->tm_isdst > 0 ? 3600 * msPerSecond : 0)); +} + + +FILE* OS::FOpen(const char* path, const char* mode) { + return fopen(path, mode); +} + + +void OS::Print(const char* format, ...) { + va_list args; + va_start(args, format); + VPrint(format, args); + va_end(args); +} + + +void OS::VPrint(const char* format, va_list args) { + vprintf(format, args); +} + + +void OS::PrintError(const char* format, ...) { + va_list args; + va_start(args, format); + VPrintError(format, args); + va_end(args); +} + + +void OS::VPrintError(const char* format, va_list args) { + vfprintf(stderr, format, args); +} + + +int OS::SNPrintF(Vector str, const char* format, ...) { + va_list args; + va_start(args, format); + int result = VSNPrintF(str, format, args); + va_end(args); + return result; +} + + +int OS::VSNPrintF(Vector str, + const char* format, + va_list args) { + int n = vsnprintf(str.start(), str.length(), format, args); + if (n < 0 || n >= str.length()) { + str[str.length() - 1] = '\0'; + return -1; + } else { + return n; + } +} + + +void OS::StrNCpy(Vector dest, const char* src, size_t n) { + strncpy(dest.start(), src, n); +} + + +char *OS::StrDup(const char* str) { + return strdup(str); +} + + +double OS::nan_value() { + return NAN; +} + + +int OS::ActivationFrameAlignment() { + // 16 byte alignment on FreeBSD + return 16; +} + + +// We keep the lowest and highest addresses mapped as a quick way of +// determining that pointers are outside the heap (used mostly in assertions +// and verification). The estimate is conservative, ie, not all addresses in +// 'allocated' space are actually allocated to our heap. The range is +// [lowest, highest), inclusive on the low and and exclusive on the high end. +static void* lowest_ever_allocated = reinterpret_cast(-1); +static void* highest_ever_allocated = reinterpret_cast(0); + + +static void UpdateAllocatedSpaceLimits(void* address, int size) { + lowest_ever_allocated = Min(lowest_ever_allocated, address); + highest_ever_allocated = + Max(highest_ever_allocated, + reinterpret_cast(reinterpret_cast(address) + size)); +} + + +bool OS::IsOutsideAllocatedSpace(void* address) { + return address < lowest_ever_allocated || address >= highest_ever_allocated; +} + + +size_t OS::AllocateAlignment() { + return getpagesize(); +} + + +void* OS::Allocate(const size_t requested, + size_t* allocated, + bool executable) { + const size_t msize = RoundUp(requested, getpagesize()); + int prot = PROT_READ | PROT_WRITE | (executable ? PROT_EXEC : 0); + void* mbase = mmap(NULL, msize, prot, MAP_PRIVATE | MAP_ANON, -1, 0); + + if (mbase == MAP_FAILED) { + LOG(StringEvent("OS::Allocate", "mmap failed")); + return NULL; + } + *allocated = msize; + UpdateAllocatedSpaceLimits(mbase, msize); + return mbase; +} + + +void OS::Free(void* buf, const size_t length) { + // TODO(1240712): munmap has a return value which is ignored here. + munmap(buf, length); +} + + +void OS::Sleep(int milliseconds) { + unsigned int ms = static_cast(milliseconds); + usleep(1000 * ms); +} + + +void OS::Abort() { + // Redirect to std abort to signal abnormal program termination. + abort(); +} + + +void OS::DebugBreak() { +#if defined (__arm__) || defined(__thumb__) + asm("bkpt 0"); +#else + asm("int $3"); +#endif +} + + +class PosixMemoryMappedFile : public OS::MemoryMappedFile { + public: + PosixMemoryMappedFile(FILE* file, void* memory, int size) + : file_(file), memory_(memory), size_(size) { } + virtual ~PosixMemoryMappedFile(); + virtual void* memory() { return memory_; } + private: + FILE* file_; + void* memory_; + int size_; +}; + + +OS::MemoryMappedFile* OS::MemoryMappedFile::create(const char* name, int size, + void* initial) { + FILE* file = fopen(name, "w+"); + if (file == NULL) return NULL; + int result = fwrite(initial, size, 1, file); + if (result < 1) { + fclose(file); + return NULL; + } + void* memory = + mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, fileno(file), 0); + return new PosixMemoryMappedFile(file, memory, size); +} + + +PosixMemoryMappedFile::~PosixMemoryMappedFile() { + if (memory_) munmap(memory_, size_); + fclose(file_); +} + + +#ifdef ENABLE_LOGGING_AND_PROFILING +static unsigned StringToLong(char* buffer) { + return static_cast(strtol(buffer, NULL, 16)); // NOLINT +} +#endif + + +void OS::LogSharedLibraryAddresses() { +#ifdef ENABLE_LOGGING_AND_PROFILING + static const int MAP_LENGTH = 1024; + int fd = open("/proc/self/maps", O_RDONLY); + if (fd < 0) return; + while (true) { + char addr_buffer[11]; + addr_buffer[0] = '0'; + addr_buffer[1] = 'x'; + addr_buffer[10] = 0; + int result = read(fd, addr_buffer + 2, 8); + if (result < 8) break; + unsigned start = StringToLong(addr_buffer); + result = read(fd, addr_buffer + 2, 1); + if (result < 1) break; + if (addr_buffer[2] != '-') break; + result = read(fd, addr_buffer + 2, 8); + if (result < 8) break; + unsigned end = StringToLong(addr_buffer); + char buffer[MAP_LENGTH]; + int bytes_read = -1; + do { + bytes_read++; + if (bytes_read >= MAP_LENGTH - 1) + break; + result = read(fd, buffer + bytes_read, 1); + if (result < 1) break; + } while (buffer[bytes_read] != '\n'); + buffer[bytes_read] = 0; + // Ignore mappings that are not executable. + if (buffer[3] != 'x') continue; + char* start_of_path = index(buffer, '/'); + // There may be no filename in this line. Skip to next. + if (start_of_path == NULL) continue; + buffer[bytes_read] = 0; + LOG(SharedLibraryEvent(start_of_path, start, end)); + } + close(fd); +#endif +} + + +int OS::StackWalk(OS::StackFrame* frames, int frames_size) { + void** addresses = NewArray(frames_size); + + int frames_count = backtrace(addresses, frames_size); + + char** symbols; + symbols = backtrace_symbols(addresses, frames_count); + if (symbols == NULL) { + DeleteArray(addresses); + return kStackWalkError; + } + + for (int i = 0; i < frames_count; i++) { + frames[i].address = addresses[i]; + // Format a text representation of the frame based on the information + // available. + SNPrintF(MutableCStrVector(frames[i].text, kStackWalkMaxTextLen), + "%s", + symbols[i]); + // Make sure line termination is in place. + frames[i].text[kStackWalkMaxTextLen - 1] = '\0'; + } + + DeleteArray(addresses); + free(symbols); + + return frames_count; +} + + +// Constants used for mmap. +static const int kMmapFd = -1; +static const int kMmapFdOffset = 0; + + +VirtualMemory::VirtualMemory(size_t size) { + address_ = mmap(NULL, size, PROT_NONE, + MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, + kMmapFd, kMmapFdOffset); + size_ = size; +} + + +VirtualMemory::~VirtualMemory() { + if (IsReserved()) { + if (0 == munmap(address(), size())) address_ = MAP_FAILED; + } +} + + +bool VirtualMemory::IsReserved() { + return address_ != MAP_FAILED; +} + + +bool VirtualMemory::Commit(void* address, size_t size, bool executable) { + int prot = PROT_READ | PROT_WRITE | (executable ? PROT_EXEC : 0); + if (MAP_FAILED == mmap(address, size, prot, + MAP_PRIVATE | MAP_ANON | MAP_FIXED, + kMmapFd, kMmapFdOffset)) { + return false; + } + + UpdateAllocatedSpaceLimits(address, size); + return true; +} + + +bool VirtualMemory::Uncommit(void* address, size_t size) { + return mmap(address, size, PROT_NONE, + MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, + kMmapFd, kMmapFdOffset) != MAP_FAILED; +} + + +class ThreadHandle::PlatformData : public Malloced { + public: + explicit PlatformData(ThreadHandle::Kind kind) { + Initialize(kind); + } + + void Initialize(ThreadHandle::Kind kind) { + switch (kind) { + case ThreadHandle::SELF: thread_ = pthread_self(); break; + case ThreadHandle::INVALID: thread_ = kNoThread; break; + } + } + pthread_t thread_; // Thread handle for pthread. +}; + + +ThreadHandle::ThreadHandle(Kind kind) { + data_ = new PlatformData(kind); +} + + +void ThreadHandle::Initialize(ThreadHandle::Kind kind) { + data_->Initialize(kind); +} + + +ThreadHandle::~ThreadHandle() { + delete data_; +} + + +bool ThreadHandle::IsSelf() const { + return pthread_equal(data_->thread_, pthread_self()); +} + + +bool ThreadHandle::IsValid() const { + return data_->thread_ != kNoThread; +} + + +Thread::Thread() : ThreadHandle(ThreadHandle::INVALID) { +} + + +Thread::~Thread() { +} + + +static void* ThreadEntry(void* arg) { + Thread* thread = reinterpret_cast(arg); + // This is also initialized by the first argument to pthread_create() but we + // don't know which thread will run first (the original thread or the new + // one) so we initialize it here too. + thread->thread_handle_data()->thread_ = pthread_self(); + ASSERT(thread->IsValid()); + thread->Run(); + return NULL; +} + + +void Thread::Start() { + pthread_create(&thread_handle_data()->thread_, NULL, ThreadEntry, this); + ASSERT(IsValid()); +} + + +void Thread::Join() { + pthread_join(thread_handle_data()->thread_, NULL); +} + + +Thread::LocalStorageKey Thread::CreateThreadLocalKey() { + pthread_key_t key; + int result = pthread_key_create(&key, NULL); + USE(result); + ASSERT(result == 0); + return static_cast(key); +} + + +void Thread::DeleteThreadLocalKey(LocalStorageKey key) { + pthread_key_t pthread_key = static_cast(key); + int result = pthread_key_delete(pthread_key); + USE(result); + ASSERT(result == 0); +} + + +void* Thread::GetThreadLocal(LocalStorageKey key) { + pthread_key_t pthread_key = static_cast(key); + return pthread_getspecific(pthread_key); +} + + +void Thread::SetThreadLocal(LocalStorageKey key, void* value) { + pthread_key_t pthread_key = static_cast(key); + pthread_setspecific(pthread_key, value); +} + + +void Thread::YieldCPU() { + sched_yield(); +} + + +class FreeBSDMutex : public Mutex { + public: + + FreeBSDMutex() { + pthread_mutexattr_t attrs; + int result = pthread_mutexattr_init(&attrs); + ASSERT(result == 0); + result = pthread_mutexattr_settype(&attrs, PTHREAD_MUTEX_RECURSIVE); + ASSERT(result == 0); + result = pthread_mutex_init(&mutex_, &attrs); + ASSERT(result == 0); + } + + virtual ~FreeBSDMutex() { pthread_mutex_destroy(&mutex_); } + + virtual int Lock() { + int result = pthread_mutex_lock(&mutex_); + return result; + } + + virtual int Unlock() { + int result = pthread_mutex_unlock(&mutex_); + return result; + } + + private: + pthread_mutex_t mutex_; // Pthread mutex for POSIX platforms. +}; + + +Mutex* OS::CreateMutex() { + return new FreeBSDMutex(); +} + + +class FreeBSDSemaphore : public Semaphore { + public: + explicit FreeBSDSemaphore(int count) { sem_init(&sem_, 0, count); } + virtual ~FreeBSDSemaphore() { sem_destroy(&sem_); } + + virtual void Wait(); + virtual void Signal() { sem_post(&sem_); } + private: + sem_t sem_; +}; + +void FreeBSDSemaphore::Wait() { + while (true) { + int result = sem_wait(&sem_); + if (result == 0) return; // Successfully got semaphore. + CHECK(result == -1 && errno == EINTR); // Signal caused spurious wakeup. + } +} + +Semaphore* OS::CreateSemaphore(int count) { + return new FreeBSDSemaphore(count); +} + +#ifdef ENABLE_LOGGING_AND_PROFILING + +static Sampler* active_sampler_ = NULL; + +static void ProfilerSignalHandler(int signal, siginfo_t* info, void* context) { + USE(info); + if (signal != SIGPROF) return; + if (active_sampler_ == NULL) return; + + TickSample sample; + + // If profiling, we extract the current pc and sp. + if (active_sampler_->IsProfiling()) { + // Extracting the sample from the context is extremely machine dependent. + ucontext_t* ucontext = reinterpret_cast(context); + mcontext_t& mcontext = ucontext->uc_mcontext; +#if defined (__arm__) || defined(__thumb__) + sample.pc = mcontext.mc_r15; + sample.sp = mcontext.mc_r13; +#else + sample.pc = mcontext.mc_eip; + sample.sp = mcontext.mc_esp; +#endif + } + + // We always sample the VM state. + sample.state = Logger::state(); + + active_sampler_->Tick(&sample); +} + + +class Sampler::PlatformData : public Malloced { + public: + PlatformData() { + signal_handler_installed_ = false; + } + + bool signal_handler_installed_; + struct sigaction old_signal_handler_; + struct itimerval old_timer_value_; +}; + + +Sampler::Sampler(int interval, bool profiling) + : interval_(interval), profiling_(profiling), active_(false) { + data_ = new PlatformData(); +} + + +Sampler::~Sampler() { + delete data_; +} + + +void Sampler::Start() { + // There can only be one active sampler at the time on POSIX + // platforms. + if (active_sampler_ != NULL) return; + + // Request profiling signals. + struct sigaction sa; + sa.sa_sigaction = ProfilerSignalHandler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_SIGINFO; + if (sigaction(SIGPROF, &sa, &data_->old_signal_handler_) != 0) return; + data_->signal_handler_installed_ = true; + + // Set the itimer to generate a tick for each interval. + itimerval itimer; + itimer.it_interval.tv_sec = interval_ / 1000; + itimer.it_interval.tv_usec = (interval_ % 1000) * 1000; + itimer.it_value.tv_sec = itimer.it_interval.tv_sec; + itimer.it_value.tv_usec = itimer.it_interval.tv_usec; + setitimer(ITIMER_PROF, &itimer, &data_->old_timer_value_); + + // Set this sampler as the active sampler. + active_sampler_ = this; + active_ = true; +} + + +void Sampler::Stop() { + // Restore old signal handler + if (data_->signal_handler_installed_) { + setitimer(ITIMER_PROF, &data_->old_timer_value_, NULL); + sigaction(SIGPROF, &data_->old_signal_handler_, 0); + data_->signal_handler_installed_ = false; + } + + // This sampler is no longer the active sampler. + active_sampler_ = NULL; + active_ = false; +} + +#endif // ENABLE_LOGGING_AND_PROFILING + +} } // namespace v8::internal diff --git a/tools/utils.py b/tools/utils.py index fa402c3..78d1e0d 100644 --- a/tools/utils.py +++ b/tools/utils.py @@ -42,7 +42,7 @@ def ReadLinesFrom(name): list.append(line) return list - + def GuessOS(): id = platform.system() if id == 'Linux': @@ -53,6 +53,8 @@ def GuessOS(): # On Windows Vista platform.system() can return 'Microsoft' with some # versions of Python, see http://bugs.python.org/issue1082 return 'win32' + elif id == 'FreeBSD': + return 'freebsd' else: return None -- 2.7.4