The Pragmatic Ball boy

iOSを中心にやってる万年球拾いの老害エンジニアメモ

FuelPHPのインストーラの中身

FuelPHPのインストール時のコマンドでなにをやっているのか調査

$ curl get.fuelphp.com/oil | sh

$ oil create fuelphp

$ curl get.fuelphp.com/oil | sh では、/usr/bin/oilにhttp://get.fuelphp.com/installer.shを書き込んでる

#!/bin/bash

PREFIX="/usr/bin/"

install_oil() {

    if [ `which sudo` ]; then
        sudo sh -c "curl --silent http://get.fuelphp.com/installer.sh > ${PREFIX}oil"
        sudo chmod +x ${PREFIX}oil
    else
        sh -c "curl --silent http://get.fuelphp.com/installer.sh > ${PREFIX}oil"
        chmod +x ${PREFIX}oil
    fi
}

#
# Handle execution
#
main() {

  # Start installation
  install_oil
  exit 0
}

main

http://get.fuelphp.com/installer.shの中身はこのようになっていて oil createが実行されたときにFuelPHPのgitリポジトリからcloneしてきてsubmodule updateしてcomposerをアップデートして、refine installでパーミッションを変更するといういたって単純なことをしてる。

#!/bin/bash

# if we have the oil script in the current directory, start that
if [ -f "./oil" ]; then
        php oil "$@"
else

                # check for bash commandline options
        if [ "$1" == "create" ]; then

                                # make sure git is installed
                if [ ! `which git` ]; then
                    echo "For this installer to work you'll need to install Git."
                    echo '        http://git-scm.com/'
                fi

                                # clone the repository, and make sure the latest master is active
                git clone --recursive git://github.com/fuel/fuel.git "./$2"
                cd ./$2
                branch=`git branch -a | grep -v "remote" | grep "master" | tail -1`
                branch=${branch#* }
                git checkout $branch
                git submodule foreach git checkout $branch

                # run composer
                php composer.phar self-update
                php composer.phar update

                # fix potential rights issues
                cd ..
                php "./$2/oil" refine install
        else
                echo 'This is not a valid Fuel installation so Oil is a bit lost.'
                echo '        http://fuelphp.com/docs/installation/instructions.html'

        fi
fi

ただgitリポジトリの最新のmasterブランチをとってくるようになっているので、 バージョンを指定したいとかは使えないですが、常に最新使うんだったら特に問題なさげ

Ruby on Railsの開発環境構築

Using Vagrant for Rails Development - GoRails のを参考に実施

VagrantVirtualBoxをインストール

Install Vagrant

Downloads – Oracle VM VirtualBox

$ vagrant plugin install vagrant-vbguest
$ vagrant plugin install vagrant-librarian-chef
$ cd MY_RAILS_PROJECT # Change this to your Rails project directory
$ vagrant init
$ touch Cheffile
site "http://community.opscode.com/api/v1"

cookbook 'apt'
cookbook 'build-essential'
cookbook 'mysql'
cookbook 'ruby_build'
cookbook 'nodejs', git: 'https://github.com/mdxp/nodejs-cookbook'
cookbook 'rbenv', git: 'https://github.com/fnichol/chef-rbenv'
cookbook 'vim'
# -*- mode: ruby -*-
# vi: set ft=ruby :

VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  # Use Ubuntu 14.04 Trusty Tahr 64-bit as our operating system
  config.vm.box = "ubuntu/trusty64"

  # Configurate the virtual machine to use 2GB of RAM
  config.vm.provider :virtualbox do |vb|
    vb.customize ["modifyvm", :id, "--memory", "2048"]
  end

  # Forward the Rails server default port to the host
  config.vm.network :forwarded_port, guest: 3000, host: 3000

  # Use Chef Solo to provision our virtual machine
  config.vm.provision :chef_solo do |chef|
    chef.cookbooks_path = ["cookbooks", "site-cookbooks"]

    chef.add_recipe "apt"
    chef.add_recipe "nodejs"
    chef.add_recipe "ruby_build"
    chef.add_recipe "rbenv::user"
    chef.add_recipe "rbenv::vagrant"
    chef.add_recipe "vim"
    chef.add_recipe "mysql::server"
    chef.add_recipe "mysql::client"

    # Install Ruby 2.1.2 and Bundler
    # Set an empty root password for MySQL to make things simple
    chef.json = {
      rbenv: {
        user_installs: [{
          user: 'vagrant',
          rubies: ["2.1.2"],
          global: "2.1.2",
          gems: {
            "2.1.2" => [
              { name: "bundler" },
              { name: "rails", version: "4.1.6" }
            ]
          }
        }]
      },
      mysql: {
        server_root_password: ''
      }
    }
  end
end

以下エラー対応

==> default: could not find recipe server for cookbook mysql
==> default:
==> default: [2015-04-04T10:18:45+00:00] ERROR: Running exception handlers
==> default: [2015-04-04T10:18:45+00:00] ERROR: Exception handlers complete
==> default: [2015-04-04T10:18:45+00:00] FATAL: Stacktrace dumped to /var/chef/cache/chef-stacktrace.out
==> default: [2015-04-04T10:18:45+00:00] ERROR: could not find recipe server for cookbook mysql
==> default: [2015-04-04T10:18:46+00:00] FATAL: Chef::Exceptions::ChildConvergeError: Chef run process exited unsuccessfully (exit code 1)
Chef never successfully completed! Any errors should be visible in the
output above. Please fix your recipes so that they properly complete.

Cheffileを以下のように修正

cookbook 'mysql','5.6'
$ vagrant provision
==> default: [2015-04-04T10:48:42+00:00] INFO: Running queued delayed notifications before re-raising exception
==> default: [2015-04-04T10:48:42+00:00] ERROR: Running exception handlers
==> default: [2015-04-04T10:48:42+00:00] ERROR: Exception handlers complete
==> default: [2015-04-04T10:48:42+00:00] FATAL: Stacktrace dumped to /var/chef/cache/chef-stacktrace.out
==> default: [2015-04-04T10:48:42+00:00] ERROR: rbenv_gem[2.1.2::bundler (vagrant)] (vagrant) (rbenv::user line 63) had an error: NoMethodError: undefined method `rbenv_rehash' for #<Chef::Provider::Package::RbenvRubygems:0x00000003e77ec8>
==> default: [2015-04-04T10:48:43+00:00] FATAL: Chef::Exceptions::ChildConvergeError: Chef run process exited unsuccessfully (exit code 1)
Chef never successfully completed! Any errors should be visible in the
output above. Please fix your recipes so that they properly complete.

cookbooks/rbenv/libraries/chef_provider_package_rbenvrubygems.rbを

def rehash
  rbenv_rehash new_resource do
    root_path rbenv_root
    user rbenv_user if rbenv_user
    action :nothing
  end.run_action(:run)
end

以下に編集

def rehash
  e = ::Chef::Resource::RbenvRehash.new(new_resource, @run_context)
  e.root_path rbenv_root
  e.user rbenv_user if rbenv_user
  e.action :nothing
  e.run_action(:run)
end
$ vagrant provision

以上でできたわけですが、chef-rbenvのリポジトリは一年以上放置されていてるようだったので、forkして修正を取り込みました

https://github.com/yanamura3/chef-rbenv

cocos2d-js 3.xでJS_CallFunctionでcrash

cocos2d-jsを2系から3系に変更した際にjavascriptのコールバックを呼ぶとexec bad accessでcrashするようになりました。

解決方法はこちらで JS_CallFunctionName crashed on iAP finished callback - Cocos2d-x Forum

JSB_AUTOCOMPARTMENT_WITH_GLOBAL_OBJCET

をJS_CallFunctionする前に呼べばよいです。

all asynchronous function call have this problem

と書いてあったのでC++から非同期でjavascriptをたたく場合にはJSB_AUTOCOMPARTMENT_WITH_GLOBAL_OBJECTをつかう必要がありそう

Objective-Cでnil結合演算子的なことをするには

Swiftだとnil結合演算子として"??"が用意されてますが、 Objective-C(というかC)でも似たようなことができます。

ここ(Conditionals - Using the GNU Compiler Collection (GCC))に書いてあるように3項演算子の真ん中を省略すると、条件式が非0の場合に条件式の値になるので

x ? x : y

x ?: y

は同じになります。(厳密にはxの評価される回数が異なりますが)

なのでObjective-C的にnilだったらデフォルト値をつっこむみたいな処理を書くと

NSString* outputString = (inputString != nil) ? inputString : @"default";

NSString* outputString = inputString ?: @"default";

このように短く書くことができます

CIのサービスいろいろ

Hound CIとかCode Climateとか知らなかったので調べたらここにCI Serviceのリストがあった。

Continuous-Integration-services/continuous-integration-services-list.md at master · ligurio/Continuous-Integration-services · GitHub

Jenkinsでゴニョゴニョやればできるけど、その手間とかメンテコストを考えると外部サービスでやれるものはやりたい。

Xcodeは年に一度はバージョンアップ問題でβ使わなければいけなかったりがあるので、iOS開発では100%外部依存することはできなかったりするところが課題

build.gradleからAndroidのバージョンベタ書きを避ける

Androidでgradleを使うと、デフォルトではこんな感じにバージョン情報がベタ書きで生成されます。

apply plugin: 'android'

android {
    compileSdkVersion 17
    buildToolsVersion "21.1.2"

    defaultConfig {
        minSdkVersion 8
        targetSdkVersion 17
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        }
    }

build.gradle一つですむプロジェクトであれば、これでもよいかもですが、マルチプロジェクトにしている場合だと変更する場合にたくさん書き換えることとなりミスの元となります。

そこで以下のようにgradle.propertiesに切り分けておきます。

gradle.properties

ANDROID_BUILD_MIN_SDK_VERSION=9
ANDROID_BUILD_TARGET_SDK_VERSION=21
ANDROID_BUILD_TOOLS_VERSION=21.1.2
ANDROID_BUILD_SDK_VERSION=21

build.gradle

android {
    compileSdkVersion Integer.parseInt(project.ANDROID_BUILD_SDK_VERSION)
    buildToolsVersion project.ANDROID_BUILD_TOOLS_VERSION

    defaultConfig {
        minSdkVersion Integer.parseInt(project.ANDROID_BUILD_MIN_SDK_VERSION)
        targetSdkVersion Integer.parseInt(project.ANDROID_BUILD_TARGET_SDK_VERSION)
    }

参考

https://github.com/facebook/facebook-android-sdk/blob/master/gradle.properties

https://github.com/facebook/facebook-android-sdk/blob/master/facebook/build.gradle

AndroidStudioでプロジェクトを開こうとするとCause: org/codehaus/groovy/runtime/typehandling/ShortTypeHandling

AndroidStudioでbuild.gradleを指定してプロジェクトを開こうとすると

Cause: org/codehaus/groovy/runtime/typehandling/ShortTypeHandling

というダイアログが表示されてプロジェクトが開かない・・・ という状態になりました。


gradle-wrapper.propertiesのgradleのバージョンが古い(1.x.xとか)とこうなるようで

gradle/wrapper/gradle-wrapper.propertiesを以下のように2.2.1にすると解決

distributionUrl=http\://services.gradle.org/distributions/gradle-2.2.1-all.zip

Android NDKでerror: 'to_string' is not a member of 'std'

androidC++のstd::to_stringを使うと以下のようなビルドエラーがでました。(環境:Android NDK r10d)

error: 'to_string' is not a member of 'std'

もちろんC++11の設定自体はできていて、他のstd::functionなどは問題なく使えます。

LOCAL_CFLAGS    := -std=c++11

NDKのlibstdc++ではまだto_stringをサポートしてない?ぽいので自前で用意するしかなさそうな

template <typename T>
std::string to_string(T value)
{
    std::ostringstream os ;
    os << value ;
    return os.str() ;
}

APP_STL を gnustl_staticではなく、stlport_staticにするとto_string使うことができましたが、C++ exception supportがない模様

The Developer's Guide | Android Developers

参考

Android ndk std::to_string support - Stack Overflow

Swiftで引数リスト(CVaListPointer)の渡し方

SwiftからObjective-Cメソッドを呼ぶ際に、引数が引数リスト(va_list)の場合、Swiftでは型が以下のようなCVaListPointerになります。

class func raise(_ name: String,
          format format: String,
      arguments argList: CVaListPointer)

結論からいきますと、getVaListという関数を使って、getVaListの引数に引数リストとして渡したいArrayを突っ込めばよいです。

NSException.raise("hoge exception", format:"exception raised %d", arguments:getVaList([1]))





以下脱線

CVaListPointerの定義を調べてみるとこのようになるほどわからん感じの構造体です。

/// The corresponding Swift type to `va_list` in imported C APIs.
struct CVaListPointer {
    init(_fromUnsafeMutablePointer from: UnsafeMutablePointer<Void>)
}

CVaListPointerを生成するためのメソッドが用意されていてこれを使う。

/// Returns a `CVaListPointer` built from `args` that's backed by
/// autoreleased storage.
///
/// .. Warning:: This function is best avoided in favor of
///    `withVaList`, but occasionally (i.e. in a `class` initializer) you
///    may find that the language rules don't allow you to use
///    `withVaList` as intended.
func getVaList(args: [CVarArgType]) -> CVaListPointer

CVarArgTypeはprotocol

protocol CVarArgType {

    /// Transform `self` into a series of machine words that can be
    /// appropriately interpreted by C varargs
    func encode() -> [Word]
}

ということからgetVaListの引数には、CVarArgTypeプロトコルを実装しているクラスのArrayということになります。

Intなどの型はCVarArgTypeをextensionで実装してるので使えます。

extension Int : CVarArgType {

    /// Transform `self` into a series of machine words that can be
    /// appropriately interpreted by C varargs
    func encode() -> [Word]
}

調べた感じだとStringは一見CVarArgTypeを実装していないように見えるんですが、ArrayにString入れても使えるのはなんでかな・・

xcodebuildでdevice向けのビルドするときのdestinationの指定方法

以前xcodebuildのパラメータの指定方法についてこちらに書きました。

Travis CIでObjective-C/Swiftのテストを実行する - Pragmatic ball boy

テストするときはシミュレータを指定するのでこれで問題ないのですが、 実機向けのstaticライブラリを用意したりするときには、destinationの指定方法が以下のように変わります。

ここで問題なのが、nameとidのところなんですが、ビルドマシンに実機を常に指しておけば指定できますが、たいていはそういう使い方しないと思います。

XcodeからビルドするときはiOS Deviceを指定すれば実機なくてもビルドだけはできるのでそれと同じことはxcodebuildでもできるはず。

xcodebuildのドキュメントを見るとgeneric/をつければ良いと書いてあります。

To build against a platform generically instead of a specific device, the destination specifier may be prefixed with the optional string "generic/", indicating that the platform should be targeted generically.

よって、以下のようにgeneric/platform=iOSと指定することでiOS Deviceを指定するのと同じようにビルドすることができます。

$ xcodebuild -scheme MyScheme -destination 'generic/platform=iOS' build

ちなみに実機とsimulator両方で使えるfat binaryを作りたい場合は、destination複数指定できるので以下のように、2つdestinationを使います。

$ xcodebuild -scheme MyScheme -destination 'generic/platform=iOS' -destination 'platform=iOS Simulator,name=iPhone 6,0S=8.0' build

fat binaryに含まれているアーキテクチャを確認するには以下のコマンドを使えばよいです。

$ xcrun lipo -info *.a

FragmentでUnfortunately, XXX has stoppedでクラッシュする

Android StudioのNew->Fragmentで追加したFragmentを使って表示しようとすると

"Unfortunately, XXX has stopped"

というポップアップが出てクラッシュするという問題が発生。 Logにも何も出ていない・・・

どこでクラッシュしてるのかさっぱりわからなかったので、仕方なく追加したFragmentにブレークポイントをはりまくって確認したところ、onAttachを最後にお亡くなりになっていた。

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            mListener = (OnFragmentInteractionListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString()
                    + " must implement OnFragmentInteractionListener");
        }
    }

Fragmentを呼ぶActivity側をOnFragmentInteractionListener interfaceに対応させないとダメのようだ。

よって以下のようにActivityにimplements XXXFragment.OnFragmentInteractionListener を追加し、onFragmentInteractionをオーバーライドすればOK。

public class XXXActivity extends ActionBarActivity implements XXXFragment.OnFragmentInteractionListener {

/// 省略

@Override
    public void onFragmentInteraction(Uri uri) {

    }

よく見たら作成されたFragmentに"Activities that contain this fragment must implement the XXXFragment.OnFragmentInteractionListener" て書いてありましたね・・、よく読めってことか

/**
 * A simple {@link Fragment} subclass.
 * Activities that contain this fragment must implement the
 * {@link XXXFragment.OnFragmentInteractionListener} interface
 * to handle interaction events.
 * Use the {@link XXXFragment#newInstance} factory method to
 * create an instance of this fragment.
 */
public class XXXFragment extends Fragment {

追記:

Fragmentを追加する際に、optionで"Include interface callbacks?"にデフォルトでチェックが入っているため、今回のようになったようでした。

f:id:yanamura:20150131231815p:plain

32bit/64bit混成バイナリをiOS5.1.1端末にインストールできない

CocoaTouch 64ビット移行ガイド より、iOS5.1.1以降で32bit/64bitの混成バイナリが利用可能と書いてあります。

しかし、ipaファイルを作ってiOS5.1.1にインストールしようとすると失敗してしまいます。

AppleのDeveloper forumに情報があったのすが、 https://devforums.apple.com/message/1071370#1071370

iOS5.1.1では64bitの入った混成バイナリのインストールが不可能で、古いデバイスに対応するためにどうやらApple側でAppStoreにサブミットされたバイナリをiOS5.1.1用に64bitバイナリを含めないように加工してるのでは、、みたいな憶測が書かれていました。

実際AppStoreからダウンロードしたらインストールできたみたいなことは書いてあったので混成バイナリであってもiOS5.1.1で動作はするっぽいですが、事前に確認できないのはちょっと気持ち悪いですね。。

なので、どうしてもiOS5.1.1で動作確認したい場合は、iOS5.1.1の評価用にだけ32bitだけでビルドするように変更しなければならないということになります。

Android Studio : Project Structure

なぜかちゃんと設定されなくて手動で設定しなおした。

Android Studioだと~/Library/Android/sdk以下にAndroid SDKがインストールされるですね

Android SDK location

~/Library/Android/sdk

JDK location

/System/Library/Frameworks/JavaVM.framework/Home

Genymotionのインストール

手元にAndroidデバイスがないけどちょっとエミュレーターで動かしたい場合に、通常のエミュレーターだと遅すぎて使い物にならないのでGenymotionを利用しています。

インストールした時の手順のメモです。

Genymotionのダウンロードとインストール

ここからダウンロード(要Login)

Genymotion

ダウンロードしたdmgファイルを開き、GenymotionとGenymotion ShellをApplicationフォルダにドラックアンドドロップ

VertualBoxのダウンロードとインストール

ここからダウンロード

Downloads – Oracle VM VirtualBox

ダウンロードしたdmgファイルを開き、VirtualBox.pkgをダブルクリックするとインストーラーが立ち上がるのでポチポチ押していけばインストールが完了します。

Genymotionの起動

ApplicationフォルダのGenymotionを開きます。

"You do not have any virtual device yet. Do you want to add a new one?" とダイアログが出るのでYesにします。

virtual deviceが何もない状態なので、下のピンクの枠をのSign inクリックしてログインします。

f:id:yanamura:20150104121435p:plain

ログインすると下のようにvirtual deviceが取得され利用できるようになります。

f:id:yanamura:20150104120806p:plain

あとは使いたいdeviceを選んで、nextを押していくとOK

そうするとvirtual deviceが作成されます

f:id:yanamura:20150104121027p:plain

上のスタートボタンを押すと起動します

一瞬で起動するのでデフォルトのエミュレータとの差は雲泥の差ですね

参考

ユーザーガイド Genymotion