Notes on Workspace Structure

Last revised  17:00 Wednesday September 26, 2001

In large Java development efforts, it is not uncommon for several inter-related projects to be under development at the same time. For the purposes of this discussion, assume teams, projects, and components align. All teams are working in the same repository, sharing a set of projects via a single stream; each project contains the source code for a single component.

Our example scenario has 4 projects: P1, P2, P3, and P4, with components C1 through C4, respectively. C1 is the only component that does not depend on any others; C2 depends on C1; C3 depends on C1 and C2; C4 depends on C2 (N.B., but not on C1 or C3).

s

We assume that there is a single CVS repository that contains all 4 projects in source code form. In addition, we assume that centralized builds are done periodically and posted to a web server where they can be downloaded as a unit. These downloads take the form of a zipped directory which includes a binary jar for each component, along with corresponding source jars to aid debugging.

We assume that each developer can download builds from the web server and install them on their local machine. To do their work, they set up Eclipse workspaces on their local machine and load one or more projects from the CVS repository. They also upgrade their workspace from time to time as new builds become available.

These assumptions are a plausible abstraction of what goes on in open source projects. (It is an open question as to how closely the Eclipse project with follow this work model.)

Developing, Using, and Patching Components

The following things are the norm for someone actively developing a component:

At the other end of the spectrum are components that are passively used: Components that are being patched are one step from being a used component in the direction of being a component being developed: The general problem can be stated as follows: each developer needs their own workspace so that they can develop their assigned component. To to do, they will need to use the components that their component depends on, and perhaps use some of the other components that depend on theirs. In order to do their work, a developer may need to patch a component that they would ordinarily just use; in some cases, they might even need to actively develop a component that they would ordinarily just use. How can the developers structure their workspaces so that they retain sufficient flexibly to switch between using and patching (or developing) these other components?

The various scenarios presented below are all plausible workspace setups, each with certain advantages and disadvantages:

Component plus libraries

project P1 (shared)
    build classpath = source /P1/C1
    output folder /P1/bin
|
project P2 (shared)
    build classpath = source /P2/C2; BUILD/c1.jar+BUILD/c1src.zip
    output folder /P2/bin
|
project P3 (shared)
    build classpath = source /P3/C3; BUILD/c1.jar+BUILD/c1src.zip; BUILD/c2.jar+BUILD/c2src.zip
    output folder /P3/bin
|
project P4 (shared)
    build classpath = source /P4/C4; BUILD/c2.jar+BUILD/c2src.zip
    output folder /P4/bin

Source for everything

project P1 (shared)
    build classpath = source /P1/C1
    output folder /P1/bin
&
project P2 (shared)
    build classpath = source /P2/C2; project P1
    output folder /P2/bin
&
project P3 (shared)
    build classpath = source /P3/C3; project P1; project P2
    output folder /P3/bin
&
project P4 (shared)
    build classpath = source /P4/C4; project P2
    output folder /P4/bin

Classpath variables to switch between libraries and sources

project P1 (shared)
    build classpath = source /P1/C1
    output folder /P1/bin
|
project P2 (shared)
    build classpath = source /P2/C2; P1_LIB
    output folder /P2/bin
|
project P3 (shared)
    build classpath = source /P3/C3; P1_LIB; P2_LIB
    output folder /P3/bin
|
project P4 (shared)
    build classpath = source /P4/C4; P2_LIB
    output folder /P4/bin

Unshared Proxy library projects

The base workspace for developers on all teams looks like:

project P1 (not shared)
    library project
    build classpath = export BUILD/c1.jar+BUILD/c1src.zip
&
project P2 (not shared)
    library project
    build classpath = export BUILD/c2.jar+BUILD/c2src.zip; project P1
&
project P3 (not shared)
    library project
    build classpath = export BUILD/c3.jar+BUILD/c3src.zip; project P1; project P2
&
project P4 (not shared)
    library project
    build classpath = export BUILD/c4.jar+BUILD/c3src.zip; project P2

None of these projects are shared; however, they have the same names as the source projects in repository. This means that any of the library projects can be replaced by loading the corresponding source project from the repository. (None of the other projects in the workspace need to change.)

project P1 (shared)
    build classpath = source /P1/C1
    output folder /P1/bin
|
project P2 (shared)
    build classpath = source /P2/C2; project P1
    output folder /P2/bin
|
project P3 (shared)
    build classpath = source /P3/C3; project P1; project P2
    output folder /P3/bin
|
project P4 (shared)
    build classpath = source /P4/C4; project P2
    output folder /P4/bin

Regarding automation, the centralized build could create a simple XML document describing the collection of proxy library projects for the workspace. Given this document, a special purpose plug-in could be written that would create (or modify existing) unshared proxy library projects in the workspace.

<projects>
    <project name="P1">
        <natures
            <nature id="org.eclipse.jdt.core.javanature"/>
        </natures>
        <libraries>
            <classpathentry kind="var" path="BUILD/c1.jar" sourcepath="BUILD/c1src.zip" export="true"/>
        </libraries>
    </project>
    <project name="P2">
        <natures
            <nature id="org.eclipse.jdt.core.javanature"/>
        </natures>
        <libraries>
            <classpathentry kind="var" path="BUILD/c2.jar" sourcepath="BUILD/c2src.zip" export="true"/>
            <classpathentry kind="project" path="/P1"/>
        </libraries>
    </project>
    <project name="P3">
        <natures
            <nature id="org.eclipse.jdt.core.javanature"/>
        </natures>
        <libraries>
            <classpathentry kind="var" path="BUILD/c3.jar" sourcepath="BUILD/c3src.zip" export="true"/>
            <classpathentry kind="project" path="/P1"/>
            <classpathentry kind="project" path="/P2"/>
        </libraries>
    </project>
    <project name="P4">
        <natures
            <nature id="org.eclipse.jdt.core.javanature"/>
        </natures>
        <libraries>
            <classpathentry kind="var" path="BUILD/c4.jar" sourcepath="BUILD/c4src.zip" export="true"/>
            <classpathentry kind="project" path="/P2"/>
        </libraries>
    </project>
</projects>

Shared proxy library projects

In the previous approach to using proxy library projects, these projects were not under VCM. In this variant of it, the proxy library projects are obtained from a repository as well. We will assume that the proxy library projects are stored in a different repository from the source projects. By assuming they're in a separate binary repository, it is easy to use the same project names for both source and binary forms (doing so in the same repository would required introducing non-standard binary-only and source-only branches in the project version histories).

The base workspace for developers on all teams looks like:

project P1 (shared via binary repository)
    library project
    build classpath = export /P1/c1.jar+BUILD/c1src.zip
&
project P2 (shared via binary repository)
    library project
    build classpath = export /P2/c2.jar+BUILD/c2src.zip; project P1
&
project P3 (shared via binary repository)
    library project
    build classpath = export /P3/c3.jar+BUILD/c3src.zip; project P1; project P2
&
project P4 (shared via binary repository)
    library project
    build classpath = export /P4/c4.jar+BUILD/c4src.zip; project P2

The proxy library projects have the same names as the source projects, but are stored in a separate repository.

project P1 (shared via source repository)
    build classpath = source /P1/C1
    output folder /P1/bin
|
project P2 (shared via source repository)
    build classpath = source /P2/C2; project P1
    output folder /P2/bin
|
project P3 (shared via source repository)
    build classpath = source /P3/C3; project P1; project P2
    output folder /P3/bin
|
project P4 (shared via source repository)
    build classpath = source /P4/C4; project P2
    output folder /P4/bin

Stub projects

This is a variation on unshared proxy library projects that makes different tradeoffs. We call these stub projects. In particular, the ability to browse a prereqisite component is traded for the ability to have only a minimal set of extra library projects in the workspace. The only difference is that stub projects do not require other projects (whereas proxy library projects did require other projects). This allow a workspace to get by with a less than complete set of stub projects. Creating a stub project never forces you to create other stub projects.

The source projects:

project P1 (shared)
    build classpath = source /P1/C1
    output folder /P1/bin
|
project P2 (shared)
    build classpath = source /P2/C2; project P1
    output folder /P2/bin
|
project P3 (shared)
    build classpath = source /P3/C3; project P1; project P2
    output folder /P3/bin
|
project P4 (shared)
    build classpath = source /P4/C4; project P2
    output folder /P4/bin

The corresponding stub projects (note the absence of required projects):

project P1 (not shared)
    library project
    build classpath = export BUILD/c1.jar+BUILD/c1src.zip

project P2 (not shared)
    library project
    build classpath = export BUILD/c2.jar+BUILD/c2src.zip

project P3 (not shared)
    library project
    build classpath = export BUILD/c3.jar+BUILD/c3src.zip

project P4 (not shared)
    library project
    build classpath = export BUILD/c4.jar+BUILD/c3src.zip

For example, a developer working on P3 would need stub projects for P1 and P2.

project P3 (shared)
    build classpath = source /P3/C3; project P1; project P2
    output folder /P3/bin
&
project P2 (not shared)
    library project
    build classpath = export BUILD/c2.jar+BUILD/c2src.zip
&
project P1 (not shared)
    library project
    build classpath = export BUILD/c1.jar+BUILD/c1src.zip

If they then decided to develop P1 as well, they would replace P1 by the souce project. Their workspace would now look like:

project P3 (shared)
    build classpath = source /P3/C3; project P1; project P2
    output folder /P3/bin
&
project P2 (not shared)
    library project
    build classpath = export BUILD/c2.jar+BUILD/c2src.zip
&
project P1 (shared)
    build classpath = source /P1/C1
    output folder /P1/bin

The fatal flaw is clear in the case of a workspace containing P4 and a stub for P2.

project P4 (shared)
    build classpath = source /P4/C4; project P2
    output folder /P4/bin
&
project P2 (not shared)
    library project
    build classpath = export BUILD/c2.jar+BUILD/c2src.zip

If, for example, a class in C4 subclasses a class in C2 which subclasses a class in C1, then the compiler will need to get its hands on the class in C1. Unfortunately, neither the source code nor binary for C1 is anywhere to be found.

Assorted Other Scenarios

A couple of other scenarios can be constructed using a combination of the techniques employed above:

Discussion

Of the various workspace setups discussed, the "Component plus libraries" approach is the most straightforward, but also the weakest. It should work well in cases where developers work on exactly the component they are assigned. But it is not recommended in cases where developers assigned to one component would need to patch or develop another component in the same workspace.

The "Source for everything" approach is best for developers who are all involved in the joint active development of all of the components. There is a limit to how large it will scale, since the cost of recompiling everything from source increases method the number and size of components. It is not recommended in situations where many of the components are not under active development; it will be more efficient to use pre-compiled binary libraries for the static components.

Approaches involving classpath variables do not have much to recommend them. They share most of the disadvantages (and none of the advantages) of using proxy library projects.

The "Proxy Library Projects" workspace setup allows each developer to work on their assigned component while providing ready access to all other components. The arrangement is flexible in allowing easy switching from using a component to patching it (or to actively developing it). The two setups outlined in detail show how the proxy binary projects can be constructed from a downloadable binary build or obtained from a version-managed repository. Each has there pluses and minuses. The main drawback of using unshared proxy library projects is the significant task of setting up a workspace in the first place. This task would have to be automated by some means; it would too tedious and error prone to have each developer create a workspace from scratch. For shared proxy library projects, the tedium is in creating new versions of the projects in their repository. This task could be automated as part of the centralized build process.

The "Stub Projects" workspace setup allows each developer to work on their assigned component while providing placeholders for other components that it depends on. The arrangement is flexible in allowing easy switching from using a component to actively developing it by replacing the placeholder. Unfortunately, this approach is fatally flawed because a compiler might not have enough information to compile. The other drawbacks are their limited useful of the stub projects (can't browse the component; can't patch the component), and the ongoing need to create additional proxy projects to fill in for the missing prerequisites as real source projects get added to the workspace.

Document History

15:45 Wednesday September 5, 2001 - first version sent for comments
13:30 Thursday September 6, 2001 - revised for first round of comments
19:40 Wednesday September 12, 2001 - automation for unshared proxy library projects
10:15 Wednesday September 26, 2001 - added stub projects
17:00 Wednesday September 26, 2001 - documented fatal flaw with stub projects