2015年8月8日 星期六

gold 和 GNU ld 行為差異造成的問題

原因

ToolChain/DSOLinking - Debian Wiki 介紹得很清楚, 主要的差異是處理 DT_NEEDED section 的行為不同。

對 program A 和 shared lib B 和 C 來說, 假設相依性如下:

  • A 用到 B 和 C
  • B 用到 C

照理說 link 的設定要如實反應出相依性。若 link 的設定如下:

  • A: link B
  • B: link C

linker 是 GNU ld (BFD-based linker) 的時候會成功, 但用 gold 的時候會失敗。原因是 linker 會在目標的 DT_NEEDED section 記錄用到的 shared lib, 然後 GNU ld 預設會將 DT_NEEDED section 的值一併「往上傳」。所以 A link B 的時候不只會在 DT_NEEDED section 加上 B 的記錄, 還會取得 B 的 DT_NEEDED section 內的值, 所以沒寫 link C 也會有 C 的記錄。

這造成一個問題: 日後 B 沒有用到 C 的時候, 會因為升級 B 而造成 A 的 link error, 錯誤原因是沒有 link C。

解法是用 GNU ld link 時也要下 --no-add-needed 避免自動取用 shared lib 內的 DT_NEEDED section 的內容。這樣開發的時候才會注意到漏加 shared lib。

這種情況容易發生嗎? 就我個人的經驗來說, 比我預期的容易。比方說寫 GTK+ 也會用到 glib, 但 GTK+ 自己也有用 glib, 所以 GTK+ 的 DT_NEEDED section 有含 glib, 然後自己就忘了加。若用 GNU ld link, 沒加 glib 也會成功。或是很多 lib 用到 pthread, 自己也用到或加入的 static lib 間接用到, 然後忘了 link pthread。

改用 gold 為預設 linker

gold link 的行為不但比較正確,速度比較快也比較省記憶體。可以的話, 改用 gold 作為系統預設 linker 比較省事。Mac OS X 已經用 gold 了, Ubuntu 12.04 要另裝 binutils-gold:

$ sudo apt-get install binutils-gold

然後檢查看看:

$ ls -l /usr/bin/ld
lrwxrwxrwx 1 root root 7 Apr  2 00:05 /usr/bin/ld -> ld.gold*

安裝前會是連到 GNU ld, 像這樣:

$ ls -l /usr/bin/ld
lrwxrwxrwx 1 root root 7 Apr  2 00:02 /usr/bin/ld -> ld.bfd*

若沒有成功, 可能要用 update-alternatives 切換預設 linker (Ubuntu 14.04 好像需要), 詳見這裡。

其它

1. GNU ld 預設會加入所有指定要 link 的 shared lib 到 DT_NEEDED section, 即使沒有用到那些 lib 也一樣。若希望只加入用到的 lib, 要加上參數 --as-needed。

2. 可以用 ldd 列出 executable 或 shared lib 相依的 lib, 像這樣:

$ ldd /bin/ls
        linux-vdso.so.1 =>  (0x00007fffe2351000)
        libselinux.so.1 => /lib/x86_64-linux-gnu/libselinux.so.1 (0x00007ff642698000)
        librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007ff642490000)
        libacl.so.1 => /lib/x86_64-linux-gnu/libacl.so.1 (0x00007ff642287000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ff641ec8000)
        libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007ff641cc4000)
        /lib64/ld-linux-x86-64.so.2 (0x00007ff6428ce000)
        libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007ff641aa6000)

2015年8月7日 星期五

PHP + JavaScript 提供 L10N 的文字

我希望的用法如下:

  • PHP 和 JavaScript 共用同一套文字格式。
  • PHP 和 JavaScript 用同一套 API。
  • 各國語系用字串 key 取出翻譯的內容。優點是可以隨意更改預設語系的內容; 缺點是網頁原始檔有一點難閱讀,無法像 L10N 前那樣直接看到內文。
  • 選用的目標語言缺內容時,改用預設語言。
  • L10N 的文字存成 JSON, 方便用各種工具編輯。
  • 函式庫相依性愈小愈好。

多數人使用的 gettext 不合我的需求, 查了一下沒找到順手的工具, 最後自己寫了一個。只有一個檔案 l10n.php, 程式不到 100 行。我不太熟 PHP 和 JavaScript, 可能寫得不怎麼道地。

用法是在網頁內 <?php require('l10n.php') ?>, 之後 PHP 和 JavaScript 都可以用 _m(KEY) 取得對到目前語言的字。可用 ?lang=LANG 切換語言,不然會以 Accept-Language 內 weight 最大的為目標語言。預設語言為 en。

API 還有一些可以改進的地方,像是支援替換文字內的變數,不用自己取出來後再替換;或是在 JSON 格式內加些輔助翻譯的描述。不過比較細微就是了,有這打算時再來參考 Chrome extension API 的作法, 還滿直覺的。

2015年8月1日 星期六

近期閱讀有物報告的心得

整理一下最近閱讀的心得,只是雜亂的片段想法。也許有天能串出更深的想法。

一個預測未來的簡單方法

原文: 有物報告 | 一個預測未來的簡單方法

用正面的方式描述是: 任何沒考慮 smartphone 的產業都會被衝擊。反過來說則是: 基於「不是人人都有 smartphone」的假設而發展出來的產業, 勢必會因 smartphone 普及而被衝擊。我比較喜歡後者的說法,前者聽起來比較空泛,後者有比較強的因果關係 (或著說比較強的推測)。

現行的例子是叫計程車。以前沒有 smartphone 要透過中間的服務人員對上雙方的位置, 幫忙湊合人和車。現在大家都有 smartphone, 定位和上網都很容易。建立平台的成本大幅下降,也不用透過服務人員配對。

去中介化的犧牲者

原文:

能用數位傳遞的東西, 終將被網路取代。書店的定位必須轉型提供不同的服務,比方說營造店內的氣氛讓人想來逛, 像是誠品。或是經由個人品味挑選書本、寫評論分享自己挑選的書。

全程創業,販售服務

原文:

販售長期的服務,金流比賣一次性的產品來得穩定。也因為更了解用戶,有機會撥掘和提供新服務取得更多獲利。理論上從上至下都自己來,使用者用起來會更順。像選用 iPhone,和 Apple TV 結合的很流暢 (其它 iCloud 之類的我沒有用, 不知效果如何)。Android phone 和 Miracast 則不容易作到流暢。

什麼都自己來, 代價是要作更多事,分散火力。如今透過 smartphone 上搭配的「商城」 (App Store、Google Play), 傳遞產品和收取費用比以前簡單許多。至少降低了一個門檻。

Btw, 這段話讓我特別有感觸,注重在賣服務,而不是工具:

哈佛商學院教授 Theodore Levitt 說:「人們要的不是鑽孔機,而是牆上的洞。」他認為購買鑽孔機是種不完美的妥協。訂閱制往往能創造企業與客戶的雙贏,因為雙方的目標是一致的。

雜談

總體來說,產品本身是數位內容, 全程都可數位化; 本身不是數位內容 (如餐廳、搭車), 背後的媒合平台也可以作些改善 (O2O, Online-to-offline)。smartphone 和行動網路的普及, 讓人們可以重新思考如何將習以為常的事作得更好。習慣的包裹愈輕, 愈容易想到新方法, 也更有機會找到更好的方法 。

看這些評論很有意思,不過對於實際作的事到是沒有直接的幫助。主要是了解目前的趨勢,偶而檢查一下自己的思維是否被舊習慣所綁住,放寬思考的方向。產品的本質還是為使用者解決他們願意付錢解決的問題。

2015年7月26日 星期日

使用 user agent 和 meta viewport 的目的和造成的影響

對行動瀏覽器和網頁的開發者來說,都希望提供最佳的使用者體驗, 不過彼此在意的事有點不同。

瀏覽器在意的事:

  • 用 user agent 告知網站自己是什麼瀏覽器, 讓網站有機會提供最佳的內容。
  • 也提供切換 user agent 的選項,若網站自動提供行動版網頁, 而使用者想看桌機版網頁時, 可換用桌機瀏覽器的 user agent 「騙」網站提供桌機版的網頁。
  • 如果頁面有用 meta viewport 設定 layout width=device width, 表示網頁有針對 device width 作最佳化, 照著辦就是了。
  • 承上, 不過若網頁沒作好, 比方說在頁面裡寫死某個元件寬度, 寫死寬度的元件仍會超出螢幕寬度, 看起來怪怪的。
  • 若沒有設 layout width=device with, 表示網頁開發者沒有針對行動裝置作最佳化, 至少要用夠寬的 layout width (>螢幕寬度), 不然網頁內容會縮成一團無法閱讀 (就像在桌機將瀏覽器視窗縮到很窄一樣)。常見的實作寬度是 980px 或 1024px。
  • 承上, 因為手機螢幕比 layout width 小, 載入完頁面後使用者無法一眼看到網頁全貌, 瀏覽器會自動縮小 (zoom out) 頁面以貼齊螢幕寬度。預期使用者可以先看見全貌, 找到有興趣的地方,再自行放大 (zoom in) 想看的部份。

網站開發者在意的事:

  • 透過 user agent 得知使用者用什麼裝置和瀏覽器。粗略分成行動裝置和桌機 (PC 或筆電) 兩種。後者通常計算能力強、網路快、螢幕大,可提供豐富內容。另外提供前者客制化的網頁,以提供更好的使用體驗。
  • 桌機網頁也可使用 Responsive Web Design (RWD), 讓大螢幕有更好的使用體驗 (但不會在意螢幕太小的表現)。不需使用 meta viewport (用了也會被桌機瀏覽器忽略)。layout width 由視窗寬度決定。
  • 行動網頁必須使用 meta viewport 要求瀏覽器用 device width 作為 layout width, 不然瀏覽器會用 980px 或 1024px 排版, 結果是字縮得太小 [*1],使用者得放大縮小外加水平捲動觀看內容, 用起來不方便。最好使用 RWD 應付不同手機的不同寬度。不然就要用比較窄的寬度為基準來排放內容, 然後在兩側或右側留白,看起來比較遜。
  • 沒餘力搞兩套網頁就用 RWD 一套通吃。雖然排版難度變高許多, 但不用寫兩套網頁流程, 應該會比較省事? 除了排版技術較深外, 可能因此對手機裝置多傳了些用不到的資料,浪費頻寬甚至拖慢載入速度。

在手機上用 desktop user agent 看到奇怪的排版內容,可能的原因:

  • 網站在桌機網頁用了 meta viewport, 但沒處理好 layout width 較窄的情況。可能的原因是同一網址會依 user agent 提供不同內容: 若是手機瀏覽器的 user agent, 就提供行動版網頁, 因此沒有測到「在手機上用 desktop user agent」的情況。
  • 網站自己有作好 RWD, 不過嵌入其它家服務的內容 (如廣告) 出槌。出槌的原因是提供內容的網站是看 user agent 決定內容, 因此提供太寬的內容而超出螢幕寬度。若嵌入的內容有依 layout width 提供內容就沒問題了。

參考資料

備註

1. 行動瀏覽器有提供 "text reflow" 的功能, 會在不改變排版的情況下, 自動放大太小的字。好處是在手機上用 980px (或1024px) 排版後,不用放大就可以看清楚主要的內文。壞處是部份內文變得比標題大, 看起來怪怪的。

印象中是 2012 或更早就有的功能, 當時看起來頗酷的也不錯用。不過在 RWD 盛行後, text reflow 大概會愈來愈少發揮效果。在行動瀏覽器 (如 Chrome 或 Puffin) 用 desktop user agent 看 Mobile 01 內文, 會看到 text reflow 的效果了。

2015年7月19日 星期日

iOS kCFErrorHTTPSProxyConnectionFailure 和 Android ERR_TUNNEL_CONNECTION_FAILED 的解法

iOS kCFErrorHTTPSProxyConnectionFailure (錯誤代碼 310) 的意思是 proxy 不允許 HTTPS 的連線,可能是 client 用 https 連往 443 以外的 port, 然後 proxy 設定不允許這樣的連線。解法是改在 443 port 執行用 https 的 web server, 或是 client 不要使用 proxy。

我用同樣的設定 (在 port 443 以外跑 https + client 用 proxy) 改測 Chrome on Android, 結果顯示的錯誤代碼是 ERR_TUNNEL_CONNECTION_FAILED, 也許這個代碼有用在其它情況。遇到的時候也可以看看是否和這有關。

2015年7月14日 星期二

Python, JavaScript, C++, Java 和 Unicode 以及 UTF-8 等編碼

基本觀念

Unicode 是一張表,定義每個文字對應的數字是什麼,比方說 'A' 是 65, '我'是 25105。

UTF-8, UTF-16, UTF-32, UCS-2, UCS-4 等則是 Unicode 的各種編碼。所謂的編碼,是某種定好的儲存格式 (或說 serialization/deserialization format 也許比較好懂?), 方便讓不同的應用程式之間傳遞資料。

比方說應用程式 A 寫入文字到檔案裡,應用程式 B 從檔案讀出文字,兩者要說好用的編碼格式 (或能從資料辨別使用的編碼格式), 才有辦法互通資料。舉例來說, 網路傳輸常用 HTTP 作為溝通協定, HTTP 有提供 client 或 server 指定傳輸內容編碼的方法 (Accept-Charset 和 Content-Type), 可以指明是用 UTF-8 或其它編碼。

寫程式的時候,基本上是假設外部讀來的資料是某種 Unicode 編碼 (最常用 UTF-8 ), 內部使用 Unicode 處理,要傳出去 (寫入硬碟/網路傳輸) 前再轉回 Unicode 編碼。用 Unicode 的好處是,可以正確計算字串長度, 這影響到使用 length, index, 取出 substring, 比對 substring (包含 regexp) 等字串 API。

這裡有以前寫的介紹,有多提一點相關的事。

Python

Python 2 就有定義 type unicode 和 type str。type str 可看成一般性的 binary, 以 byte 為單位。Python 2 的字串 '...' 的型別是 str, 要特別用 u'...' 才會改用 unicode。

有了 type unicode, 處理 unicode 比較輕鬆:

  1. 外部讀進來的字 (UTF-8) 存在 type str, 馬上轉成 unicode
  2. 函式的輸入輸出都用 unicode (len(), [] 的行為正確)
  3. 要輸出到螢幕、檔案、網路時再轉成 UTF-8。

參考 All About Python and Unicode 和 Unicode in Python: Common Pitfalls 了解細節。

另外, Python 可能用 UCS-2 或 UCS-4 實作 Unicode, 可從 sys.maxunicode 的值得知, 65535 表示用 UCS-2。因為 UCS-2 / UCS-4 每個字分別占 2 / 4 bytes, 對使用的記憶體的量有很大的影響,不同作業系統會有不同考量, 像 Mac OS X 用 UCS-2, Ubuntu 12.04 用 UCS-4。然後在 Python 3.3 後都支援 UCS-4 了 (因為有實作 PEP 0393 -- Flexible String Representation)。

例如 u'𝌆' 會被當成兩個字 [*1]:

In [1]: u'我'
Out[1]: u'\u6211'

In [2]: len(u'我')
Out[2]: 1

In [3]: u'𝌆'
Out[3]: u'\U0001d306'

In [4]: len(u'𝌆')
Out[4]: 2

Btw, Python 3 改變字串預設的型別: '...' 是 unicode, 要用 b'...' 才會用 str,用意是希望大家預設就用 Unicode 避免混亂,不過卻造成 2 升 3 的一些問題。直接引用以前碎碎唸的心得:

在 python2.7 踩到 python3 unicode 的雷,import unicode_literals 會讓 'abc' 自動變成 u'abc' ( 原本的行為是 ‘abc' == b'abc') ,難怪怎麼看都不太對勁,third-party code 寫 'abc',別的 third-party package 預期收到 raw string 卻收到 unicode string, 於是回報 TypeError: 'unicode' does not have the buffer interface

用 ipdb 除錯時覺得相當奇怪,一樣的程式碼 (如 s = 'abc'),在 ipdb 內執行得到的結果是 raw string, 但原本的程式執行時卻變 unicode ....

搜了一下才看到也有不少人反應這點: Proposal to not use unicode_literals #22 https://github.com/PythonCharmers/python-future/issues/22

JavaScript

JavaScript’s internal character encoding: UCS-2 or UTF-16?: 詳細到不行的介紹,沒耐性就直接看文中的 conclusion 吧。

結論是 JavaScript 直接用 UCS-2 表示字元, 不像 Python 分成 str 和 unicode。開發者幾乎不用操心 Unicode、UTF-8 之類的事, 反正在 JavaScript 內都是用 UCS-2 表示。缺點是超出 65535 的 Unicode 不會被正確處理:

> '我'.length
1
> '𝌆'.length
2

不過網頁還是可以正確顯示字的外觀,大概是因為瀏覽器內部用 UTF-16 處理,字的 Unicode 仍然是對的,畫字時有選對 font glyph。但是受限於 JavaScript 的語言規範,只能斷成多個字表示。

C++

C++ 沒有定義型別 Unicode, 要自己用 std::string, std::wstring 或其它型別表示。

查到幾種轉換方法

  • 用 C++11
  • 用 Boost
  • 用 icu
  • 用 utf8-cpp

最後一個作法最簡單, include 幾個 header 檔即可,不像其它方法要改 compiler 參數或引入一大包函式庫。所以我後來是用 utf8-cpp, 待要非得用到其它方法時再來研究為啥需要其它方法。stackoverflow 裡有很多討論 (由此可見 C++ 使用 Unicde 的方法有多混亂....), 這是其中一篇。

Java

Java 的基本單位 char 一樣用 UCS-2 實作, 但 String 提供另外的 API 可正確存取 Unicode。直接引用別人的結論:

Han Guokai wrote on 20th January 2012 at 18:28:

In Java5, ‘char’ means code unit (16-bit), not character (code point). One character uses one or two chars. String’s length method return the char count. And there are some additional methods based on code points, i.e. codePointCount. See: http://docs.oracle.com/javase/1.5.0/docs/api/java/lang/String.html

比方說 Java String 提供 charAt 和 codePointAt。後者是正確的 Unicode 字元,而前者是 char, 遇到 UCS-2 外的字一樣會出錯。

結論

總算弄清楚過去常用的四種語言實作 Unicode 和 Unicode 編碼的方法,結果四種語言有四套作法, 真是出乎意料出外...。推薦閱讀JavaScript’s internal character encoding: UCS-2 or UTF-16? 了解 BMP, supplementary planes 和 surrogate pairs, 會比較清楚 JavaScript 和 Java 使用 UCS-2 作為字元定義遇到的問題 (為了省記憶體, 是必要的 trade-off)。

備註

1. Blogspots 無法正式顯示這個字, 可從這裡看到測試用的字。https://codepoints.net/ 可找到更多範例。

2015/07/19 更新

經 Scott 指正, 更新內文對 Python Unicode 實作的描述。

2015年7月11日 星期六

從舊有的子目錄建立新的 git repository

如果要搬移的目錄在 repository 的最上層,可以用 git subtree。若是要搬的目錄是中間某層目錄 (例: a/b/c 的 c), 可以用 filter-branch。

筆記一下我操作成功的流程:

1. 在 git server 上建立新的 headless repository

git-host$ cd /path/to/repo && mkdir new_repo && cd new_repo && git init --bare

2. 在自己的機器上從原本的 repository filter 出要搬移的目錄。注意: 目錄內的內容會變成只剩目標目錄,我是 clone 一份新的來作。

myhost$ git clone ssh://path/to/old_repo
myhost$ cd old_repo
# Filter the master branch to path/to/folder and remove empty commits
myhost$ git filter-branch --prune-empty --subdirectory-filter path/to/folder master
Rewrite 48dc599c80e20527ed902928085e7861e6b3cbe6 (89/89)
Ref 'refs/heads/master' was rewritten

3. 合併舊有的 git commits 到新的 git repository 並更新回 server

myhost$ cd /path/to/somewhere
myhost$ git clone ssh://path/to/new_repo
myhost$ cd new_repo
myhost$ git pull /path/to/old_repo # 取得剛才 filter 過的 old_repo
myhost$ git push --set-upstream origin master
myhost$ rm -rf /path/to/old_repo  # 移除用不到的目錄

在 Fedora 下裝 id-utils

Fedora 似乎因為執行檔撞名,而沒有提供 id-utils 的套件 ,但這是使用 gj 的必要套件,只好自己編。從官網抓好 tarball ,解開來編譯 (./configure && make)就是了。 但編譯後會遇到錯誤: ./stdio.h:10...