Android 프로젝트를 진행하게 되어서 개발 환경을 구축하게 되었는데, 이것이 그다지 간단한 작업이 아니었다. 물론, 안드로이드 개발자 사이트에 개발 환경을 구축하는 방법을 자세히 설명해 놓았지만, 3번 이상은 하지 않고 싶은 작업이다. 또한, 우리 본부내에서 안드로이드 프로젝트에 참여하는 사람들이 설치에 관해 이것 저것 물어보고, 설치 메뉴얼을 제대로 따라하지 않아서 문제가 많이 발생하는 것을 보고, “아~ 이건 아니다~”라고 생각했다.
그때 나타난 우리의 구세주~~~ 우리 부서에서 관리하는 성능이 좀 괜찮은 우분투 서버가 있었는데, 창진형이 그 서버에 이클립스를 설치하고 x11을 통해 원격으로 접속하게 환경을 갖춰 놓은 것을 보고, “아!!! 이거다!!!” 라는 생각이 들었다. 그래서 그 우분투 서버에 안드로이드 플랫폼 개발 환경을 모두 정상적으로 갖춰두고, “자~ 여러분들 이제 힘들게 개발 환경 따로 구축하지 말고, 여기에서 개발하면 됩니다!”… 내 생각에는 좋은 환경이었으나, 생각외로 리눅스라는 환경이 좀 귀찮았나보다. 현재 우분투에 구축된 개발 환경을 통해 개발하는 사람은 창진형과 나밖에 없다…ㅋㅋ
이 글의 주제는 이것이 아니고, 최근 회사에서 스마트폰 프로젝트 열풍이 불어서 맥북을 하나 얻었는데, 이게 개발 환경으로는 더 없이 좋은 것 같다. 운영체제도 깔끔하고 내장되어 있는 소프트웨어도 깔끔하고… 그래서 안드로이드 프로젝트도 여기서 개발하려고 환경을 갖추다가, 어떻게 하면 가장 깔끔한 원격 개발 환경을 갖출 수 있을까 고민하다가 알아낸 정보들을 정리하고자 한다. 우분투 서버에 개발 환경 구축하는 방법은 필요하면 다음에 포스팅 하도록 하겠다.
Android app 개발을 위한 툴은 이클립스를 사용할 것이기 때문에, Mac에서 이클립스를 통해 개발하는 방법을 간단히 요약하면 다음과 같다. 그리고 이후부터 Android 개발 환경이 구축되어 있는 서버를 “ubuntu”라 부르고, 실제 개발을 위해 사용하려는 pc를 “mac”이라 부르겠다.
- -
즉, ssh client를 통해 ubuntu에 접속하고 ubuntu에서 eclipse process를 구동시킨다. 그리고 eclipse app의 화면 출력은 x11 forwarding을 통해 mac의 x11 server로 전송하고, x11 server는 전송된 데이터를 이용해 ubuntu에서 실행된 eclipse 프로그램을 mac에 띄우게 된다.
물론 위의 방법은 x11 forwarding기능을 이용한 ssh tunneling을 통한 방법이다. 그 이외에 xhost나 xauth를 통해 실행하는 방법도 있다. 하지만 그 두가지 방법은 보안상으로 취약하다고 하기때문에 일단 ssh를 이용한 방법부터 설명하고 나머지 둘은 나중에 설명하도록 한다.
-
- ssh를 이용한 x11이용 방법
1. ubuntu 측의 필요한 설정
우선 x11 forwarding 기능을 이용하려면 ubuntu측의 sshd설정 중, X11Forwarding 플래그의 값을 yes로 설정해야 한다. 일반적 설정의 ubuntu는 /etc/ssh/sshd_config 파일에 sshd 관련 설정들이 있다. X11Forwarding 플래그는 보안상의 이유로 주석 처리 되어있는데, 주석을 풀고 yes로 바꾸자. 물론 시스템의 보안이 최우선인 서버에서는 이것을 풀지 말아야 하고, 이 서버를 통한 개발도 하지 말아야 한다. 신중히 선택하자. 이 플래그를 바꾸면 x11을 다시 구동시켜야 한다.
ubuntu에 접속할 계정에 DISPLAY 환경 변수가 설정되어 있는지 확인한다. ssh 메뉴얼의 x11 forwarding 부분의 설명에도 나와있지만, DISPLAY 환경 변수는 설정되어 있지 말아야 x11 forwarding이 제대로 동작한다.
2. mac에서 x11을 구동시킨다.
eclipse를 실행시키는 입장은 ubuntu가 서버이고 mac이 클라이언트이지만, x11의 입장에서는 mac이 서버이고 ubuntu가 클라이언트이다. 즉, mac에서 실행된 x11 서버가, 각종 x11 클라이언트들로부터 전송된 x11 어플리케이션들을 화면에 띄우게 된다. 따라서 원격으로 eclipse를 실행시키기 전에, mac에서 x11 서버를 먼저 띄워야 한다.
x11은 dock > 응용프로그램 > 유틸리티에 있다.
3. ubuntu에 접속하고, eclipse를 실행
x11을 실행시키면 기본적으로 xterm이 뜬다. 다른 터미널로 접속해도 상관이 없지만, 이왕 뜬거니 그냥 여기에서 접속하자. xterm에서 다음을 실행한다. 참고로, 예전에는 ssh -X 옵션을 사용했었는데, 보안상의 문제로 -X 옵션이 참조하는 x11 forwarding은 표준에서 제외되었다. 대신 -Y옵션을 이용한다. -Y옵션은 trust x11 forwarding이다.
위에서 “네트워크 클라이언트에서의 연결을 허용”에 체크해 주면 된다.
하지만 실제로 테스트해보니, 환경설정 창에서의 설정은 먹혔는데, defaults를 이용한 세팅은 먹히지 않았다. 아마 개인적인 생각으로는 환경설정 창의 설정이 우선 시 되는 것 같다. 예전에 ubuntu 서버에서도 그런 현상이 있었던 것으로 봐서는…
설정을 바꾼 뒤에는 x11을 다시 실행시켜야 적용된다. x11을 종료시키고 다시 실행시키자.
2. ubuntu 측의 필요한 설정
x11 forwarding에서와는 달리, xhost를 이용한 방식은 eclipse 어플리케이션을 디스플레이 할 위치를 환경변수에 설정해 줘야 한다. 일반적으로 ubuntu에 접속할 계정의 홈디렉토리의 .bashrc에 다음을 입력해서 자동으로 설정되게 한다.
export DISPLAY=mac의주소:0
이제, 위의 해당 계정으로 접속해서 x11 어플리케이션을 실행시키면 항상 mac에 뜨게 된다.
3. eclipse를 실행시키자
xhost를 통한 접근이 가능하게 하려면, 접근하는 x11클라이언트(ubuntu)를 접근 가능하게 등록해 줘야 한다. mac에서 다음을 실행하자.
xhost +ubuntu주소
위에서 말했다시피, xhost는 보안에 아주 취약하다. 보내어지는 정보가 암호화 되지 않기때문에 mac에서 입력하는 정보들을 고스란히 캡춰당할 수 있다. 아주 안전한 접속에서만 이 방법을 사용하자.
이제 ubuntu에 접속하여, eclipse를 실행해 보자.
eclipse &
잘 실행된다.
xauth는… 알아봐야 할 것들이 많아서 다음에 설명해야 할 것 같다(사실은 포스팅하는데 시간이 너무 걸려, 눈치가 보여서…ㅋㅋ)
어쨌든 개발 환경을 하나로 구축해 놓으니 정말 환상이다. 처음에는 windows가 설치된 pc에서 개발하다가, 다른 환경에서 개발하려니, 개발 환경부터 소스 복사까지 귀찮은 일이 한 두가지가 아니었는데, 이제는 어떤 환경에서건 ubuntu 서버로 접속만 하면 끝이다. 또한 구글에서 Android sdk 및 ndk들을 자주 업그레이드 하는데, ubuntu 서버에서만 업그레이드 해주면 모든 사용자들이 업그레이드 된 sdk를 쓸 수 있다. 물론 eclipse Android plug-in 설정만 바꾸면 개인 sdk도 사용 가능하다. 역시 이래서 linux계열 운영체제와 gnu를 좋아한다…ㅋ
4 Responses to “x11을 통해 원격으로 Android 프로젝트 개발을 해보자”
위에 제가 언급한 애들은 cygwin하고 아무 상관 없습니다. 글자그대로 X서버만 딸랑 실행됩니다. 윈도우용 터미널 에뮬레이터를 사용해서 X용 프로그램을 직접 실행해줘야 합니다. (뒤집어서 말하면 cygwin같이 POSIX레이어를 제공하고 어쩌고가 아니라 그냥 X서버만 달랑…)
xming 은 내부적으로 설정 파일 에서 plink를 이용한 rsh(over ssh)를 지원합니다. 원격지 서버의 X용 프로그램을 아예 단축 아이콘으로 만들어 버릴수 있습니다.
nested template class의 inner class를 다루다보면 이상한 컴파일 오류에 직면하게 된다.
여기서 이상하다는 것은 몇번을 봐도 틀린 곳이 없는데 컴파일러가 오류를 리턴하는 경우다.
그것도 이상한 오류메세지로…
사실 이건 그다지 크지 않은 내용이라 해결할때마다 원인과 해결책에 대해 글을 쓰지 않았는데, 이렇게 글을 쓰는 이유는, 오류메세지가 실제 오류와는 무관한 내용이라, 이 문제에 부딪힐때마다 무슨 오류였는지 기억이 나지 않아서 반복적인 삽질을 하기때문에 시간 낭비를 하지 않기 위해서이다.
참고로 이러한 오류를 발생시키는 컴파일러는 gnu gcc 컴파일러 버전 4.x이다. visual studio 6.0컴파일러에서는 이러한 오류가 발생하지 않았는데, visual studio 2005부터는 어느정도 c++표준을 따르려고 노력했기때문에 이런 오류가 나타날 가능성은 있을 것 같다.
다음의 코드를 보자.
template<int __A, int __A2, int __B>
class Outer {
public:
template<int __InA>
class Inner1 {
public:
Inner1() : _a(__InA) {}
int print() { printf("val : %d\n", _a); }
int _a;
};
public:
Outer() : _b(__B) {}
int print() { printf("val : %d\n", _b); }
public:
int _b;
};
template<int __AA, int __BB>
class Test {
public:
typedef Outer<__AA, __AA, __BB>::template Inner1<__AA> TestInner;
public:
Test() : _b(__AA) {}
public:
int print() { printf("val : %d\n", _b); }
int _b;
};
보기에는 이상이 없어 보인다. 하지만 컴파일러는 다음의 오류를 리턴한다.
g++ -c duple.cpp
In file included from duple.cpp:1:
duple.h:27: error: type ‘Outer<__AA, __AA, __BB>’ is not derived from type ‘Test<__AA, __BB>’
duple.h:27: error: expected ‘;’ before ‘TestInner’
오류메세지를 보면, 이해가 잘 되지 않는다. “Outer가 Test로부터 파생되지 않았다?” Outer가 Test로부터 파생되도록 사용된 부분은 없는데… 그리고 “TestInner 앞에 ;가 빠졌다?” 보통 이런 오류는 정의되지 않은 뭔가가 있을때 나오는데, 위에서 정의되지 않은 부분은 없다. 왜 이런 오류를 리턴할까?
원인을 찾다보니 결국은 c++의 모호성때문이라는 결론을 냈다. 즉, 위에서 typedef문에 있는 Outer는 class Test의 template parameter들에 의해 type이 결정되고, Outer의 type이 결정되어야 Inner1에 대해 해석할 수 있는 것이다. 우리가 눈으로 보며 생각하기에는, class Test를 사용할 때 template parameter들이 주어질 것이고, 이 parameter들에 의해 Outer type이 정의될 것이며, Outer type이 정의되면 Inner1에 대한 해석도 가능해 보인다. 하지만 컴파일러는 이것을 잘 해석하지 못하는 것 같다.
따라서 이를 해결하려면 Inner1이 type이라는 것을 명시적으로 선언해 줄 필요가 있다. 이러한 명시적 선언은 typename keyword를 통해서 할 수 있다.
이 부분에 대해서 IBM홈페이지에 정리되어 있는 다음의 내용을 참고하자.
The typename Keyword
Use the keyword typename if you have a qualified name that refers to a type and depends on a template parameter. Only use the keyword typename in template declarations and definitions. The following example illustrates the use of the keyword typename:
template<class T> class A
{
T::x(y);
typedef char C;
A::C d;
}
The statement T::x(y) is ambiguous. It could be a call to function x() with a nonlocal argument y, or it could be a declaration of variable y with type T::x. C++ will interpret this statement as a function call. In order for the compiler to interpret this statement as a declaration, you would add the keyword typename to the beginning of it. The statement A::C d; is ill-formed. The class A also refers to A<T> and thus depends on a template parameter. You must add the keyword typename to the beginning of this declaration:
typename A::C d;
You can also use the keyword typename in place of the keyword class in template parameter declarations.
요약하면, template parameter에 의존적인 type으로 한정된 명칭에 대해 언급할때, typename keyword를 써야한다는 것이다.
위 상황과 비교하면, typedef문에서 Inner1 type에 대한 새로운 type 정의를 하는데, Outer와 Inner1이 template parameter에 의존적이기 때문에 정의앞에 typename keyword를 붙여야 한다는 것이다. 수정된 코드는 다음과 같다.
template<int __A, int __A2, int __B>
class Outer {
public:
template<int __InA>
class Inner1 {
public:
Inner1() : _a(__InA) {}
int print() { printf("val : %d\n", _a); }
int _a;
};
public:
Outer() : _b(__B) {}
int print() { printf("val : %d\n", _b); }
public:
int _b;
};
template<int __AA, int __BB>
class Test {
public:
typedef typename Outer<__AA, __AA, __BB>::template Inner1<__AA> TestInner;
public:
Test() : _b(__AA) {}
public:
int print() { printf("val : %d\n", _b); }
int _b;
};
g++ -c duple.cpp
In file included from duple.cpp:1:
duple.h:27: error: non-template ‘Inner1′ used as template
duple.h:27: note: use ‘Outer<__AA, __AA, __BB>::template Inner1′ to indicate that it is a template
duple.h:27: error: declaration does not declare anything
이 부분은 컴파일러가 친절하게도 정확한 오류를 리턴해 준다. 즉, template이 아닌 Inner1을 tempalte처럼 사용했다는 것이다. 우리가 눈으로 보기에는 Inner1은 template이지만, 컴파일러는 Inner1이 template member인지 아니면 일반 member인지 알지 못하는 것 같다. 따라서 우리는 Inner1이 template이란걸 명시해 줄 필요가 있다.
이 부분에 있어서도 IBM홈페이지에 정리된 내용이 있는데, 다음과 같다.
The Keyword template as Qualifier
Use the keyword template as a qualifier to distinguish member templates from other names. The following example illustrates when you must use template as a qualifier:
class A
{
public:
template<class T> T function_m() { };
};
The declaration char object_x = argument.function_m<char>(); is ill-formed. The compiler assumes that the < is a less-than operator. In order for the compiler to recognize the function template call, you must add the template quantifier:
If the name of a member template specialization appears after a ., ->, or :: operator, and that name has explicitly qualified template parameters, prefix the member template name with the keyword template. The following example demonstrates this use of the keyword template:
#include <iostream>
using namespace std;
class X {
public:
template <int j> struct S {
void h() {
cout << “member template’s member function: ” << j << endl;
}
};
template <int i> void f() {
cout << “Primary: ” << i << endl;
}
};
template<class T> void g(T* p) {
p->template f<100>();
p->template f<20>();
typename T::template S<40> s; // use of scope operator on a member template
s.h();
}
int main()
{
X temp;
g(&temp);
}
The following is the output of the above example:
Primary: 100
Specialized, non-type argument = 20
member template’s member function: 40
If you do not use the keyword template in these cases, the compiler will interpret the < as a less-than operator. For example, the following line of code is ill-formed:
p->f<100>();
The compiler interprets f as a non-template member, and the < as a less-than operator.
요약하면, member template과 일반 member를 구분하기 위해서 template 한정자를 사용하라는 것이다.
위, 내용을 보면 재미있는 부분이 있는데,
class A
{
public:
template<class T> T function_m() { };
};
template<class U> void function_n(U argument)
{
char object_x = argument.template function_m<char>();
}
이렇게 쓸 수 있다는 것은 나도 아직 몰랐다. 지금까지 리턴값에 대한 template parameter는 모호해서 불가능 할 것이라 생각했는데, 이런식으로 리턴값에 대한 모호성을 해결하는 방법이 있다는 것은 정말 재미있는 사실이다.
참고로, “.”뿐만 아니라 “->”, “::”에도 template 한정자를 사용할 수 있다. 즉, “Class1::template Class2<T>”와 “argument->template function_m<char>”가 가능하다는 것이다.
이러한 컴파일러 제약 사항은 c++0x 표준 제정 및 향상된 컴파일러로, 앞으로 개발자가 고민하지 않아도 될 내용일 수 있어서 삽질이라는 생각을 할 수 있지만, 지금의 컴파일러가 처리하지 못하는 부분에 대해 생각해 보는 것도 재미있는 것 같아서 의미가 있을 수 있을 것 같다는 생각이든다.
세상에는 인연이란 것과 운명이란 것이 존재한다.
크리스마스 이브에 내게 나타난 기적같은 선물... 그 짧은 시간 안에 내 삶의 전부가 되고 있다...
이제 내게 있어 행복이란, 기적과 같이 내게 주어진
이 운명을 지켜 나가는 것이다.
내 삶의 목표는 명확해졌다. 이제부터 남은 것은,
이를 위해 내가 무엇을 할 수 있느냐이다.
티끌 같이 보잘 것 없는 나이지만, 나의 행복을 위해 태산이 되어 볼 것이다.
좀 묵은 글이지만…
굳이 사족을 달자면 윈도우에서도 위처럼 X서버를 쓸 수 있습니다.
돈이 있다면 – http://www.netsarang.co.kr/products/xmg_detail.html
돈이 없다면 – http://sourceforge.net/projects/xming/
저는 그냥 리눅스 씁니다 ㅋㅋ
네~ 윈도우에서도 설치해서 쓰고 있습니다…ㅎㅎ
근데 예전에 cygwin때문에 엄청 삽질해서 고생했던 적이 있어서,
그때의 영향으로 리눅스 쉘을 사용해야 하는 개발 환경은 무조건 cygwin이외의 환경을 좋아해서..ㅎㅎ
엄… 오해가 발생할까봐 또한번 사족을 답니다.
위에 제가 언급한 애들은 cygwin하고 아무 상관 없습니다. 글자그대로 X서버만 딸랑 실행됩니다. 윈도우용 터미널 에뮬레이터를 사용해서 X용 프로그램을 직접 실행해줘야 합니다. (뒤집어서 말하면 cygwin같이 POSIX레이어를 제공하고 어쩌고가 아니라 그냥 X서버만 달랑…)
xming 은 내부적으로 설정 파일 에서 plink를 이용한 rsh(over ssh)를 지원합니다. 원격지 서버의 X용 프로그램을 아예 단축 아이콘으로 만들어 버릴수 있습니다.
결론은 OSX에 포함된 거지같은 X11보다 훨씬 좋습니다. ^^
아.. 제가 OS X를 싫어한다든가 하진 않습니다.
Java, python, perl은 항상 맥에서 작업합니다. ㅎㅎㅎ