2013年8月31日 星期六

SQLite Full-Text Searches 與 Custom Tokenizers 筆記 (FTS3/FTS4)

雖然敝公司的強項就是做 search engines 跟資料儲存管理,但研究其他家的 search 機制也是很基本的功夫。SQLite 小歸小,卻也五臟俱全,應該稱得上市面用戶最廣的資料庫系統吧?光 browser 或 mobile phone 就打趴一大片。

SQLITE Full-text Search 官方介紹網址: SQLite FTS3 and FTS4 Extensions

簡言之 FTS4 比 FTS3 更有效率,但也更吃資料空間。要留意的版本資狀態:
FTS4 is an enhancement to FTS3. FTS3 has been available since SQLite version 3.5.0 in 2007-09-04. The enhancements for FTS4 were added with SQLite version 3.7.4 on 2010-12-08.
至於 FTS3/FTS4 使用時機:
FTS4 is sometimes significantly faster than FTS3, even orders of magnitude faster depending on the query, though in the common case the performance of the two modules is similar. FTS4 also offers the enhanced matchinfo() outputs which can be useful in ranking the results of a MATCH operation. On the other hand, in the absence of a matchinfo=fts3 directive FTS4 requires a little more disk space than FTS3, though only a percent of two in most cases.
建立 FTS3/FTS4 Tables:

-- Create an FTS table named "pages" with three columns:
CREATE VIRTUAL TABLE pages USING fts4(title, keywords, body);

-- Create an FTS table named "mail" with two columns. Datatypes
-- and column constraints are specified along with each column. These
-- are completely ignored by FTS and SQLite.
CREATE VIRTUAL TABLE mail USING fts3(
  subject VARCHAR(256) NOT NULL,
  body TEXT CHECK(length(body)<10240)
);


至於 FTS Queries 範例:

-- The examples in this block assume the following FTS table:
CREATE VIRTUAL TABLE mail USING fts3(subject, body);

SELECT * FROM mail WHERE rowid = 15;                -- Fast. Rowid lookup.
SELECT * FROM mail WHERE body MATCH 'sqlite';       -- Fast. Full-text query.
SELECT * FROM mail WHERE mail MATCH 'search';       -- Fast. Full-text query.
SELECT * FROM mail WHERE rowid BETWEEN 15 AND 20;   -- Slow. Linear scan.
SELECT * FROM mail WHERE subject = 'database';      -- Slow. Linear scan.
SELECT * FROM mail WHERE subject MATCH 'database';  -- Fast. Full-text query.


以及這段:

-- Example schema
CREATE VIRTUAL TABLE mail USING fts3(subject, body);

-- Example table population
INSERT INTO mail(docid, subject, body) VALUES(1, 'software feedback', 'found it too slow');
INSERT INTO mail(docid, subject, body) VALUES(2, 'software feedback', 'no feedback');
INSERT INTO mail(docid, subject, body) VALUES(3, 'slow lunch order',  'was a software problem');

-- Example queries
SELECT * FROM mail WHERE subject MATCH 'software';    -- Selects rows 1 and 2
SELECT * FROM mail WHERE body    MATCH 'feedback';    -- Selects row 2
SELECT * FROM mail WHERE mail    MATCH 'software';    -- Selects rows 1, 2 and 3
SELECT * FROM mail WHERE mail    MATCH 'slow';        -- Selects rows 1 and 3


接著,提提要使用 FTS3 / FTS4 的用法,須要在 compile sqlite3 加上設定才行,預設沒有啟用,而啟用 FTS3 也會啟用 FTS4:

-DSQLITE_ENABLE_FTS3
-DSQLITE_ENABLE_FTS3_PARENTHESIS




CPPFLAGS="-DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_FTS3_PARENTHESIS" ./configure <configure options>

此外,切 token 預設可選的機制有 "simple", "porter" (Porter Stemming algorithm), "icu" (需要SQLITE_ENABLE_ICU, 要有C版ICU Library), 和 "unicode61" (SQLite version 3.7.13之後支援)。

-- Create an FTS table named "papers" with two columns that uses
-- the tokenizer "porter".
CREATE VIRTUAL TABLE papers USING fts3(author, document, tokenize=porter);

-- Create an FTS table with a single column - "content" - that uses
-- the "simple" tokenizer.
CREATE VIRTUAL TABLE data USING fts4(tokenize=simple);

-- Create an FTS table with two columns that uses the "icu" tokenizer.
-- The qualifier "en_AU" is passed to the tokenizer implementation
CREATE VIRTUAL TABLE names USING fts3(a, b, tokenize=icu en_AU);


其中 simple 就是會把資料都先轉成小寫,查詢時也會將 keyword 轉小寫;porter 則是支援相關字詞的搜尋,例如資料內是存 "frustrated" 字,但用 "Frustrated" 和 "Frustration” 都可以查到;icu library 全名則為 "International Components for Unicode" library;Unicode61 則跟 simple 很像,但支援 unicode,如 unicode 空白與鏢體符號(切 token 用的 delimiter)等,而 simple 只認得 ASCII 部分。

接著,進入正題 SQLite FTS 這迷人的架構就在於可自定 tokenizers 啦,也是這篇筆記的重點,Custom (User Implemented) Tokenizers,不過網站上只有片段程式碼,問了同事才知道接下來又要翻 code 才行 :P 於是建議我挑 simple_tokenizer 來看,在 sqlite3.c 中,可以搜尋 tokenizers 的描述:
The fts3 built-in tokenizers - "simple", "porter" and "icu"- are implemented in files fts3_tokenizer1.c, fts3_porter.c and fts3_icu.c respectively. The following three forward declarations are for functions declared in these files used to retrieve the respective implementations.
Calling sqlite3Fts3SimpleTokenizerModule() sets the value pointed to by the argument to point to the "simple" tokenizer implementation.
And so on.
故搜尋 fts3_tokenizer1.c 可得知以下的定義:

/*
** 2006 Oct 10
**
** The author disclaims copyright to this source code.  In place of
** a legal notice, here is a blessing:
**
**    May you do good and not evil.
**    May you find forgiveness for yourself and forgive others.
**    May you share freely, never taking more than you give.
**
******************************************************************************
**
** Implementation of the "simple" full-text-search tokenizer.
*/

typedef struct simple_tokenizer {
  sqlite3_tokenizer base;
  char delim[128];             /* flag ASCII delimiters */
} simple_tokenizer;

typedef struct simple_tokenizer_cursor {
  sqlite3_tokenizer_cursor base;
  const char *pInput;          /* input we are tokenizing */
  int nBytes;                  /* size of the input */
  int iOffset;                 /* current position in pInput */
  int iToken;                  /* index of next token to be returned */
  char *pToken;                /* storage for current token */
  int nTokenAllocated;         /* space allocated to zToken buffer */
} simple_tokenizer_cursor;

static int simpleDelim(simple_tokenizer *t, unsigned char c){
  return c<0x80 && t->delim[c];
}
static int fts3_isalnum(int x){
  return (x>='0' && x<='9') || (x>='A' && x<='Z') || (x>='a' && x<='z');
}

/*
** Create a new tokenizer instance.
*/
static int simpleCreate(
  int argc, const char * const *argv,
  sqlite3_tokenizer **ppTokenizer
){
// ...
}

/*
** Destroy a tokenizer
*/
static int simpleDestroy(sqlite3_tokenizer *pTokenizer){
  sqlite3_free(pTokenizer);
  return SQLITE_OK;
}

/*
** Prepare to begin tokenizing a particular string.  The input
** string to be tokenized is pInput[0..nBytes-1].  A cursor
** used to incrementally tokenize this string is returned in
** *ppCursor.
*/
static int simpleOpen(
  sqlite3_tokenizer *pTokenizer,         /* The tokenizer */
  const char *pInput, int nBytes,        /* String to be tokenized */
  sqlite3_tokenizer_cursor **ppCursor    /* OUT: Tokenization cursor */
){
// ...
}

/*
** Close a tokenization cursor previously opened by a call to
** simpleOpen() above.
*/
static int simpleClose(sqlite3_tokenizer_cursor *pCursor){
  simple_tokenizer_cursor *c = (simple_tokenizer_cursor *) pCursor;
  sqlite3_free(c->pToken);
  sqlite3_free(c);
  return SQLITE_OK;
}

/*
** Extract the next token from a tokenization cursor.  The cursor must
** have been opened by a prior call to simpleOpen().
*/
static int simpleNext(
  sqlite3_tokenizer_cursor *pCursor,  /* Cursor returned by simpleOpen */
  const char **ppToken,               /* OUT: *ppToken is the token text */
  int *pnBytes,                       /* OUT: Number of bytes in token */
  int *piStartOffset,                 /* OUT: Starting offset of token */
  int *piEndOffset,                   /* OUT: Ending offset of token */
  int *piPosition                     /* OUT: Position integer of token */
){
// ...
}

/*
** The set of routines that implement the simple tokenizer
*/
static const sqlite3_tokenizer_module simpleTokenizerModule = {
  0,
  simpleCreate,
  simpleDestroy,
  simpleOpen,
  simpleClose,
  simpleNext,
  0,
};

/*
** Allocate a new simple tokenizer.  Return a pointer to the new
** tokenizer in *ppModule
*/
SQLITE_PRIVATE void sqlite3Fts3SimpleTokenizerModule(
  sqlite3_tokenizer_module const**ppModule
){
  *ppModule = &simpleTokenizerModule;
}


從底部反過來往上看,至少要實作 Create, Destroy, Open, Close 和 Next,除了初始化跟資源釋放外,最重要的就是 Next 這個部分,簡單的說,就是把一串 byes 交給 Next,而 Next 每次被呼叫時,就從指定的位置往後找出一個 token 出來回傳,以此完成架構。

來做一個簡單的 changyy tokenizer 好了,只會把文章內有 changyy 開頭的 token 都取出來,其餘都 skip 掉:

/*
** Extract the next token from a tokenization cursor.  The cursor must
** have been opened by a prior call to simpleOpen().
*/
static int changyyNext(
  sqlite3_tokenizer_cursor *pCursor,  /* Cursor returned by simpleOpen */
  const char **ppToken,               /* OUT: *ppToken is the token text */
  int *pnBytes,                       /* OUT: Number of bytes in token */
  int *piStartOffset,                 /* OUT: Starting offset of token */
  int *piEndOffset,                   /* OUT: Ending offset of token */
  int *piPosition                     /* OUT: Position integer of token */
){
  changyy_tokenizer_cursor *c = (changyy_tokenizer_cursor *) pCursor;
  changyy_tokenizer *t = (changyy_tokenizer *) pCursor->pTokenizer;
  unsigned char *p = (unsigned char *)c->pInput;

  while( c->iOffset<c->nBytes ){
    int iStartOffset;

    /* Scan past delimiter characters */
    while( c->iOffset<c->nBytes && simpleDelim(t, p[c->iOffset]) ){
      c->iOffset++;
    }

    /* Count non-delimiter characters. */
    iStartOffset = c->iOffset;
    while( c->iOffset<c->nBytes && !simpleDelim(t, p[c->iOffset]) ){
      c->iOffset++;
    }

    if( c->iOffset>iStartOffset ){
      int i, n = c->iOffset-iStartOffset;
      if( n>c->nTokenAllocated ){
        char *pNew;
        c->nTokenAllocated = n+20;
        pNew = sqlite3_realloc(c->pToken, c->nTokenAllocated);
        if( !pNew ) return SQLITE_NOMEM;
        c->pToken = pNew;
      }
      for(i=0; i<n; i++){
        /* TODO(shess) This needs expansion to handle UTF-8
        ** case-insensitivity.
        */
        unsigned char ch = p[iStartOffset+i];
        c->pToken[i] = (char)((ch>='A' && ch<='Z') ? ch-'A'+'a' : ch);
      }
      *ppToken = c->pToken;
      *pnBytes = n;
      *piStartOffset = iStartOffset;
      *piEndOffset = c->iOffset;
      *piPosition = c->iToken++;
      if( !strncmp(c->pToken,"changyy", n > 7 ? 7 : n ) )
      return SQLITE_OK;
    }
  }
  return SQLITE_DONE;
}


上述程式取於 simple_tokenizer 架構,只在做後的 token 加上判斷而已 :) 如此一來,假設測資為 "changyy changes changyys world",那搜尋 changyy 跟 changyys 都可以找到,但搜尋 changes 和 world 都會找不到(因為不符合 token 規定 :P)

$ ./sqlite3
sqlite> .load /path/changyy.so
sqlite> CREATE VIRTUAL TABLE data USING fts3(tokenize=changyy);
sqlite> INSERT INTO data ('content') VALUES ('changyy changes changyys world');
sqlite> SELECT SNIPPET(data) FROM data WHERE data MATCH 'changyy';
<b>changyy</b> changes changyys world
sqlite> SELECT SNIPPET(data) FROM data WHERE data MATCH 'changes';
sqlite> SELECT SNIPPET(data) FROM data WHERE data MATCH 'changyys';
changyy changes <b>changyys</b> world
sqlite> SELECT SNIPPET(data) FROM data WHERE data MATCH 'world';
sqlite>


可以看到上述只有當 token 有被索引過的才能找出來,如此一來,就算完成偷懶的 custom tokenizer 學習吧 XD 更多操作請參考 https://github.com/changyy/sqlite-study 

2013年8月25日 星期日

[電影] 海苔億萬富翁 / The Billionaire / Top Secret / 泰國「小老板海苔」故事



大概可能有快半個月以上,在老家看了這齣電影,但只看了一半沒看完。昨晚想起來再繼續在 PPS 上看完 XD 這大概就跟電影 社群網戰(Social Network) 類似的創業型電影,但我比較喜歡小老闆海苔的故事。

小老闆海苔嗎?第一次吃是在當兵時期,同事推薦我吃看看,果真口感很不一樣呢,不過老家看電影時,也跑去買個兩包來吃,當時的心情就油了點,看了電影才會認清原來是油炸(過油)的啊。

最近周邊也有類似性格的人,我很佩服他們可以這樣不停地、不放棄地去執行所想的理想,回想起前陣子在 fb 聊天,還被學弟的老爸念了一頓:

「你們這樣跟PTT鄉民整天嘴砲有啥兩樣!」 

真是當頭棒喝啊!哈。但能夠這樣執行的人,真的是另一種幸福啊~

對小老闆海苔有興趣的人,可以多看幾篇簡介(大概是當時電影宣傳文章):

2013年8月24日 星期六

[Linux] 讓 Gitweb 支援 Markdown 顯示 @ Ubuntu 12.04

Gitweb & Markdown

之前同事很常用 Markdown 寫筆記,希望公司用的 gitweb 可以支援,所以我就稍微下海改一下 Perl 啦 XD 在改之前也有上網查了一下,其實 gitweb 本身已經有一個架構可以展現 repo/READM.html 的功能,其實搭配 git 可以弄成每次 project commit 後,自動將某個 markdown 檔案產生對應的 HTML 來用。

可在 /usr/share/gitweb/index.cgi 搜尋 READM.html 找到這片段程式碼:

# If XSS prevention is on, we don't include README.html.
# TODO: Allow a readme in some safe format.
if (!$prevent_xss && -s "$projectroot/$project/README.html") {
        print "<div class=\"title\">readme</div>\n" .
              "<div class=\"readme\">\n";
        insert_file("$projectroot/$project/README.html");
        print "\n</div>\n"; # class="readme"
}


但如此一來就侷限一個 project 只能展現的單一 markdown 檔案,思考一會後,我就選擇依照 gitweb 架構,在處理檔案時的 raw 項目,依樣畫葫做出一個 markdown 選項 XD

做法:
  1. 安裝 markdown ,讓系統支援 markdown 指定
  2. 找尋 sub git_blob_plain 程式碼,複製一份成 sub git_blob_markdown
  3. 找尋 raw 關鍵字,可以在 sub git_blob 中看到選單,只需在 raw 和 HEAD 中間插入 markdown 選單,並註冊 our %actions 處理此動作
  4. 修改 git_blob_markdown ,把它搞成讓 markdown 指令過水一下即可
片段程式碼:

our %actions = (
        ...
        "blob_plain" => \&git_blob_plain,
        "blob_markdown" => \&git_blob_markdown,
        ...
);


git_blob 片段程式碼:

$formats_nav .=
       $cgi->a({-href => href(action=>"history", -replay=>1)},
              "history") .
      " | " .
       $cgi->a({-href => href(action=>"blob_plain", -replay=>1)},
              "raw") .
      " | " .
      $cgi->a({-href => href(action=>"blob_markdown", -replay=>1)},
               "markdown") .
      " | " .

      $cgi->a({-href => href(action=>"blob",
                            hash_base=>"HEAD", file_name=>$file_name)},
              "HEAD");


git_blob_markdown:

sub git_blob_markdown {
        my $type = shift;
        my $expires;
        if (!defined $hash) {
                if (defined $file_name) {
                        my $base = $hash_base || git_get_head_hash($project);
                        $hash = git_get_hash_by_path($base, $file_name, "blob")
                                or die_error(404, "Cannot find file");
                } else {
                        die_error(400, "No file name defined");
                }
        } elsif ($hash =~ m/^[0-9a-fA-F]{40}$/) {
                # blobs defined by non-textual hash id's can be cached
                $expires = "+1d";
        }

        open my $fd, "git ".git_cmd()." cat-file blob $hash | markdown | "
        #open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
                or die_error(500, "Open git-cat-file blob '$hash' failed");

        $type = "text/html";

        # "save as" filename, even when no $file_name is given
        my $save_as = "$hash";
        if (defined $file_name) {
                $save_as = $file_name;
        } elsif ($type =~ m/^text\//) {
                $save_as .= '.txt';
        }

        my $sandbox = $prevent_xss &&
                $type !~ m!^(?:text/[a-z]+|image/(?:gif|png|jpeg))(?:[ ;]|$)!;


        print $cgi->header(
                -charset => 'UTF-8',
                -type => $type,
                -expires => $expires
                );
        local $/ = undef;
        binmode STDOUT, ':raw';
        print <$fd>;
        binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi
        close $fd;
}


比較特別的是...我還沒有真的搞懂 gitweb 或 perl 架構,像在 git_blob_markdown 裡頭,原本的架構是用 open my $fd, "-|", ... 繼續的,但我試了很久總搭不起來,就先偷懶用 "git" 代替了 :P 此外,若要增加 markdown 輸出的顯示,如 CSS 等,可以在 git_blob_markdown 下方的 printf <$fd>; 前面,去印一些 CSS 結構囉

2013年8月21日 星期三

[Linux] 增加 stack size 解決 g++ 編譯問題: Out of memory / virtual memory exhausted: Cannot allocate memory @ Ubuntu 12.04

最近在編譯同事的 C++ 程式時常常 crash 掉,噴訊息:
g++: internal compiler error: Killed (program cc1plus)
Please submit a full bug report,
with preprocessed source if appropriate.
See <file:///usr/share/doc/gcc-4.6/README.Bugs> for instructions.
追了一會兒後,才發現自己編譯 boost 1.54.0 也碰過這種事,查看 dmesg 有類似的訊息:
Out of memory: Kill process 5309 (cc1plus) score 327 or sacrifice child
Killed process 5309 (cc1plus) total-vm:720204kB, anon-rss:18672kB, file-rss:8kB
例如編譯 boost 1.54.0:
~/boost_1_54_0$ "g++"  -ftemplate-depth-128 -O3 -finline-functions -Wno-inline -Wall -pthread -fPIC -fno-strict-aliasing -ftemplate-depth-1024 -DBOOST_ALL_NO_LIB=1 -DBOOST_CHRONO_DYN_LINK=1 -DBOOST_DATE_TIME_DYN_LINK=1 -DBOOST_FILESYSTEM_DYN_LINK=1 -DBOOST_LOG_DYN_LINK=1 -DBOOST_LOG_SETUP_BUILDING_THE_LIB=1 -DBOOST_LOG_SETUP_DLL -DBOOST_LOG_USE_NATIVE_SYSLOG -DBOOST_LOG_USE_SSSE3 -DBOOST_LOG_WITHOUT_EVENT_LOG -DBOOST_SPIRIT_USE_PHOENIX_V3=1 -DBOOST_SYSTEM_DYN_LINK=1 -DBOOST_SYSTEM_NO_DEPRECATED -DBOOST_THREAD_BUILD_DLL=1 -DBOOST_THREAD_DONT_USE_CHRONO=1 -DBOOST_THREAD_POSIX -DBOOST_THREAD_USE_DLL=1 -DDATE_TIME_INLINE -DNDEBUG  -I"." -c -o "bin.v2/libs/log/build/gcc-4.6/release/build-no/log-api-unix/threading-multi/settings_parser.o" "libs/log/src/settings_parser.cpp"
噴訊息:
virtual memory exhausted: Cannot allocate memory
增加 stack-size 後 (-Wl,--stack=0x2000000),即可解決:

~/boost_1_54_0$ "g++" -Wl,--stack=0x2000000 -ftemplate-depth-128 -O3 -finline-functions -Wno-inline -Wall -pthread -fPIC -fno-strict-aliasing -ftemplate-depth-1024 -DBOOST_ALL_NO_LIB=1 -DBOOST_CHRONO_DYN_LINK=1 -DBOOST_DATE_TIME_DYN_LINK=1 -DBOOST_FILESYSTEM_DYN_LINK=1 -DBOOST_LOG_DYN_LINK=1 -DBOOST_LOG_SETUP_BUILDING_THE_LIB=1 -DBOOST_LOG_SETUP_DLL -DBOOST_LOG_USE_NATIVE_SYSLOG -DBOOST_LOG_USE_SSSE3 -DBOOST_LOG_WITHOUT_EVENT_LOG -DBOOST_SPIRIT_USE_PHOENIX_V3=1 -DBOOST_SYSTEM_DYN_LINK=1 -DBOOST_SYSTEM_NO_DEPRECATED -DBOOST_THREAD_BUILD_DLL=1 -DBOOST_THREAD_DONT_USE_CHRONO=1 -DBOOST_THREAD_POSIX -DBOOST_THREAD_USE_DLL=1 -DDATE_TIME_INLINE -DNDEBUG  -I"." -c -o "bin.v2/libs/log/build/gcc-4.6/release/build-no/log-api-unix/threading-multi/settings_parser.o" "libs/log/src/settings_parser.cpp"

對於編譯 boost 時,或許可以這樣用(更正確的用法應該是 LD_FLAGS):

~/boost_1_54_0$ ./bootstrap.sh
~/boost_1_54_0$ CXX="g++ -Wl,--stack=0x2000000" ./b2 -j2

2013年8月16日 星期五

[OSX] 使用 MacPorts 安裝 Boost 1.54.0 和 wxWidgets 2.9.5 (c++11, std=c++11, stdlib=libc++) @ Mac 10.8

最近用有案子 Boost 1.54.0 和 wxWidgets 2.9.5 來開發 OSX app,而採用 MacPorts 預設安裝 boost 跟 wxwidgets30 都只是預編好的,就算用 port -s 參數編出來的仍不支援 C++11。

然而編譯 boost 1.54.0 支援 c++11 也可以考慮用 brew 來安裝,只要加參數即可:

$ ruby -e "$(curl -fsSL https://raw.github.com/mxcl/homebrew/go)"
$ brew info boost
boost: stable 1.54.0 (bottled), HEAD
http://www.boost.org
Not installed
From: https://github.com/mxcl/homebrew/commits/master/Library/Formula/boost.rb
==> Options
--universal
        Build a universal binary
--with-c++11
        Compile using Clang, std=c++11 and stdlib=libc++
--with-icu
        Build regexp engine with icu support
--with-mpi
        Build with mpi support
--without-python
        Build without python support
--without-single
        Disable building single-threading variant
--without-static
        Disable building static library variant
$ brew install boost --with-c++11


但 brew 裡的 wxwidgets 只有到 2.9.4 版:

$ brew info wxwidgets
wxmac: stable 2.9.4.0
http://www.wxwidgets.org
Not installed
From: https://github.com/mxcl/homebrew/commits/master/Library/Formula/wxmac.rb
==> Options
--without-python
        Build without python support


摸索了一下 MacPorts 後,可以透過參數編譯使之支援 C++11:

$ port info boost wxwidgets30
boost @1.54.0 (devel)
Variants:             debug, [+]no_single, [+]no_static, openmpi, python25, python26, [+]python27, python31, python32, python33,
                      regex_match_extra, universal

Description:          Boost provides free portable peer-reviewed C++ libraries. The emphasis is on portable libraries which work
                      well with the C++ Standard Library.
Homepage:             http://www.boost.org

Library Dependencies: zlib, expat, bzip2, libiconv, icu, python27
Platforms:            darwin
License:              Boost-1
Maintainers:          nomaintainer@macports.org
--
wxWidgets30 @2.9.5_2 (graphics, devel)
Variants:             aui, debug, monolithic, [+]sdl, stdlib, universal

Description:          wxWidgets is a mature open-source cross-platform C++ GUI framework for Mac OS, Unix, Linux, Windows. It can
                      make use of a variety of native widget sets as well as its own widget set: Mac OS, GTK+, Motif, WIN32.
                      wxWidgets will even run on embedded systems using Linux and X11.
Homepage:             http://www.wxwidgets.org/

Library Dependencies: jpeg, tiff, libpng, zlib, libiconv, expat, libsdl, libsdl_mixer
Conflicts with:       wxgtk, wxWidgets
Platforms:            darwin
License:              wxwidgets-3.1
Maintainers:          jwa@macports.org, mojca@macports.org

$ sudo port install wxwidgets30
$ sudo port uninstall wxwidgets30
$ sudo port install -s wxWidgets30 configure.cc=clang configure.cxx="clang++ -std=c++11 -stdlib=libc++" configure.cxxflags="-std=c++11 -stdlib=libc++ -O2" configure.objcxxflags="-std=c++11 -stdlib=libc++ -O2"

$ sudo port install boost
$ sudo port uninstall boost
$ sudo port install -s boost configure.cc=clang configure.cxx="clang++ -std=c++11 -stdlib=libc++" configure.cxxflags="-std=c++11 -stdlib=libc++ -O2" configure.objcxxflags="-std=c++11 -stdlib=libc++ -O2"