Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.
RetroTech 팟캐스트 44BITS 팟캐스트

Scala와 Java의 순환의존성 문제에 대한 컴파일

최근 봄싹에서 스칼라 스터디를 시작했는데 주말에 모이는 스터디외에 온라인에서 퀴즈를 진행하기로 했습니다. 첫번째 문제를 풀다가 궁금해서 테스트 해보면서 정리해봅니다. 퀴즈는 아래와 같습니다.

자바와 스칼라 코드가 함께 존재하고, 또 서로에게 의존 관계가 있는 프로젝트가 있다고 가정하자. 만일 모든 파일이 같은 디렉토리에 존재하고, 빌드는 전혀 이루어지지 않은 상태라면 어떤 순서로 컴파일하면 될까?

1. javac *.java; scalac *.scala
2. scalac *.scala; javac *.java
3. javac *.java *.scala; scalac *.scala
4. scalac *.java *.scala; javac *.java
5. javac *.java *.scala; scalac *.java *.scala

헷갈리는 문제죠. 자바와 스칼라 모두 컴파일하면 .class로 되고 자바에서도 스칼라 소스를 사용할 수 있고 스칼라에서도 자바소스를 사용할 수 있습니다. 이렇다 보니 서로 의존성을 가지고 있을 경우에는 어떤 파일을 먼저 컴파일해야하는가 하는 문제가 생기게 되고 위 문제는 이 경우 어떻게 컴파일을 해야하는가 입니다.

2개의 파일을 만들었습니다.

// foo.scala
class Foo

class Baz extends Bar


// Bar.java
public class Bar extends Foo {}


Scala는 Java파일의 클래스인 Bar를 참조하고 있고 Java소스는 scala파일에서 정의된 Foo를 참조하고 있습니다.



1. javac *.java; scalac *.scala
사용자 삽입 이미지

당연한 결과입니다. bar.java를 컴파일하려고 하는데 Foo를 찾지 못해서 cannot fine symbol 오류가 발생합니다.


2. scalac *.scala; javac *.java
사용자 삽입 이미지

foo.scala를 컴파일하면서 Bar를 찾지 못해서 not found: type Bar 오류가 발생합니다.


3. javac *.java *.scala; scalac *.scala
사용자 삽입 이미지

Bar.java에서는 1번과 동일한 오류가 나고 자바컴파일러이므로 foo.scala를 그냥 오류나버립니다.(오류메시지가 정확히 머라하는지 모르겠군요.)


4. scalac *.java *.scala; javac *.java
사용자 삽입 이미지

scalac로 java와 scala파일을 같이 컴파일해주면 정상적으로 잘 컴파일이 됩니다.(자바와 스칼라의 순서는 상관없는것 같습니다.)


5. javac *.java *.scala; scalac *.java *.scala
첫명령어가 3번과 동일하기 때문에 같은 오류가 발생합니다.



일단 이 퀴즈의 답은 4번입니다.(아직 퀴즈내신 분이 답을 알려주진 않았지만) 일단 4번으로 하면 서로 의존하는 문제를 해결하고 컴파일이 가능하고 4번의 스크린샷에서 나온 javac 컴파일의 오류는 좀 다른 문제로 보입니다. javac컴파일에서 처음 나왔던 ScalaObject 오류에 대해서는 아래의 이유때문에 생긴것입니다.


import java.rmi.RemoteException;
import scala.ScalaObject;
import scala.ScalaObject.class;

public class Foo
  implements ScalaObject
{
  public int $tag()
    throws RemoteException
  {
    return ScalaObject.class.$tag(this);
  }
}

Foo.class를 역컴파일하면 위와 같은 코드가 나타납니다. ScalaObject를 사용하고 있기 때문에 4번 스크린샷의 첫번째 오류는 이 ScalaObject가 없어서 나타난 오류입니다. 이제 문제는 그 다음인데 ScalaObject는 Scala가 설치된 폴더의 lib/scala-library.jar 파일안에 있기 때문에 이파일과 함께 컴파일하면 해당 문제는 해결되지만 마지막에 Foo를 찾지 못하는 컴파일 오류는 저의 Scala지식수준으로는 잘 모르겠습니다만 위의 퀴즈의 정답과는 다른 문제가 아닌가 싶습니다.(이 무책임.. ㄷㄷ)  Programming Scala에 보면( "cannot find symbol"로 검색하면 나오는 부분입니다.) 싱글톤 오브젝트로 해결하는 부분이 있는데 이부분과 관련이 있지 않나 싶습니다.



이 퀴즈는 제가 다 푼건 아니고 Joint Compilation of Scala and Java Sources페이지를 참고해서 테스트해본 내용입니다. 위의 예제는 이 페이지에서 빌려온 예제입니다. 이 페이지에 따르면 Scala 2.7.2에서 Scala와 Java간의 Joint Compilation을 지원하고 있다고 합니다.  이 예제에서 간단한 해결책은 foo.class의 파일을 2개로 나누어서 컴파일 하는 것이지만 실제로 파일을 분리하기 어려운 순환의존성(circular dependency)을 가지는 문제는 쉽게 찾을수 있습니다.

이 순환문제를 해결하기 위해서는 스칼라컴파일러가 Bar클래스에 대해서 알아야 합니다. scalac는 Scala소스를 파싱하고 분석하여 지원되는 Java소스를 찾아냅니다.(AST를 이용한다는것 같은데 다 이해하진 못하겠네요.) scalac가 분석할때 모든 자바노드는 discard됩니다. 이 순간에 순환의존성 문제는 해결됩니다. 이후 Bar클래스는 실제로 컴파일 된것이 아니고 scalac가 분석(analyzed)만 한것이기 때문에 javac로 .java 소스파일들은 컴파일 해 주어야 합니다.


덧) 4번에서 javac 컴파일문제 누가 알려주시면 감사하겠습니다. ㅠㅠ

덧) 2010.5.15 내용 추가합니다.
위의 컴파일 오류문제를 네피림님의 도움으로 해결했습니다. javac를 쓰지 않고 IDE에만 의존해서 살아왔던 폐혜로 인한 저의 민망한 실수였습니다. Foo.class가 있는 위치가 클래스패스에 잡히지 않아서 생긴 문제인데 위의 스샷에는 그냥 간단히 찍었지만 여러가지로 테스트해보았는데 다 안되서 스칼라라이브러만 클래스패스로 잡아서 스샷을 찍은 것이었습니다.

javac -classpath %SCALA_HOME%/lib/scala-library.jar;. *.java

jar뒤에 ;. (세미콜론 마침표) 를 찍어서 현재 폴더의 위치를 클래스패스에 포함시켰습니다. 저 위에 joint compliation에 대한 문서에 :(콜론)으로 되어 있어서 테스트를 콜론으로만 했었습니다. 네피림님께 듣고 보니 $SCALA_HOME 라고 쓴것을 보니 환경이 리눅스환경이었나 보군요. 위 명령어로 컴파일 하면 컴파일 잘 됩니다.
2010/05/14 03:21 2010/05/14 03:21