views:

812

answers:

6

What is the best way to share Delphi source files among projects?

Clarification: We want to use a single source file in multiple Delphi projects. We've been using our SCM tool to put the same file into multiple folders, but this is not a super-elegant experience and we are also considering migrating to a tool that doesn't support this.

As I’ve been investigating this question, I’ve considered a few different approaches, but I’d like to know what you’re doing and how you find your approach.

Important Scenarios:

  • Code-time
    • Adding a new sharing dependency should require explicit declaration, so that sharing is managed.
    • Adding a new sharing dependency should still be relatively simple; it should not require a complicated process.
      • One file which lists all of the project’s “imported” files (from externally) would be nice.
  • Compile-time
    • All projects should always build with the one current version (current as of the source sync state plus local edits).
      • (Maintaining different versions in different locations should use file branching, which is not the topic, here.)
    • Whether each project should be able to affect the shared file’s compilation with different compiler settings (including flags) is arguable.
      • It’s arguably easier to maintain (i.e. long-term) source code that is always built consistently.
      • It’s arguably easier to make maintenance fixes (i.e. short-term) if the scope of said changes can easily be restricted to one project.
  • Debug-time
    • The correct version of the source should automatically open, when stepping into a routine or setting a breakpoint.
    • Editing the displayed source should affect the next build.
      • We do not want to debug against a temporary copy of the source: we'd probably lose code, in the confusion.

Considerations:

  • Near-Term:
    • What approach will be simplest to put in place?
  • Long-Term:
    • What approach will be simplest to use and maintain?

Thanks, in advance, for your feedback!

Mattias


--- UPDATE ---

Thanks for your feedback, via answers, comments, and votes!

I've started down the path of putting shared files into one "producer" project and importing a list of compiled files into each "consumer" project. The projects are being linked together with MSBuild. Once things are more nailed-down, I'll edit this question and the "Library Project" answer, to share what I've learned.

Stay tuned! (But don't hold your breath; you'll asphyxiate within minutes! :P )

+7  A: 

Use Source Control System's File Sharing Feature

  • Pro: Fast and easy to set up, if the SCM system supports it.
  • Pro/Con: Each consumer project can independently affect compile-time.
  • Con: There is no official location, in the local working copy of sources.
    • This can lead to confusion.
  • Con: Source changes are not reflected in other locations until checkin and re-retrieve.
    • To properly verify other projects, before checkin, is possible but a royal pain in the butt.
  • Con: Not all SCM systems support shared files.
    • Subversion’s closest feature is folder-level svn:externals.

(Edit: Retitled this to avoid confusion. Of course, everyone should use Source Control! :-) )

Mattias Andersson
A: 

Copy-on-compile

  • Pro: File sharing can be managed file-by-file.
  • Pro/Con: Each consumer project can independently affect compile-time.
  • Con: Debugger will link to the temporary copy, not the official version.
    • TODO: See whether there is some way to change this.
  • Con: May take some work to set up the MSBuild projects.
  • Con: May be difficult to incrementally build shared files.
    • May involve rewriting some of Delphi’s MSBuild rules.
Mattias Andersson
A: 

Copy-Compile-Delete

  • Pro: Only one official copy of each file, to edit.
  • Pro: Debugger will not link to the temporary copy, since it has been deleted by debug-time.
    • TODO: Verify that the debugger will find the original source, if we put it in the “Browsing Path”.
  • Pro: File sharing can be managed file-by-file.
  • Pro/Con: Each consumer project can independently affect compile-time.
  • Con: May take some work to set up the MSBuild projects.
  • Con: May be difficult/impossible to incrementally build shared files.
    • May involve rewriting some of Delphi’s MSBuild rules.
Mattias Andersson
+3  A: 

Use a Library Project

  • Pro: Only ever one copy of each file to potentially edit.
  • Pro: Only one compile, for each source file.
    • Less time to compile.
    • Consistent compilation among dependent projects.
    • Natural incremental builds.
  • Pro: Debugger naturally links to proper source.
    • TODO: Confirm.
  • Pro/Con: Consumer projects can not independently affect compile-time.
  • Con: May be difficult to manage sharing at a file-by-file level.
    • TODO: Investigate.
  • Con: Could take significant effort to set up.
    • Setup of MSBuild projects.
    • Required project settings must be centralized and these changes must be verified.
Mattias Andersson
I use library projects stored WITH source control. For shared units, I just include the path to the library source directory in the library search paths.
skamradt
Since all the files are on the library path, does each of your projects have access to most every shared file? Or do you have *so many* library projects that you share only one file at a time?(And Source Control is absolutely necessary! I have clarified what I meant in the other approach. :-) )
Mattias Andersson
No, the Project specific search paths takes care of the directories which are localized to say, a specific version or group of programs. For the everyday every program must use routines, those are in the global search path,
skamradt
Another fact to mention. ANY "source" which is considered library/shared is also considered interface immutable. I will add to the interface, but never change it unless I create a new library unit. I program mostly to interfaces, so this isn't much of a problem for me.
skamradt
Interesting! Thanks for sharing your approach to sharing. Our file sharing is currently very unstructured, so implementing something like what you do would unfortunately take a large amount of work.
Mattias Andersson
Since what you do seems rather different than what I meant by this approach (I meant dump what we have in a library project; you control your sharing via immutable interfaces and carefully-designed libraries), feel free to break your approach out into a new "answer". Thanks!
Mattias Andersson
+1  A: 

I think no special solutions are required. In our project (several applications that share large areas of code) we use the following approach:

  1. Split source code to folders.
  2. Create packages for logical units of shared code.
  3. Support monolith (without using packages) and parted builds.
  4. Monolith builds are used for coding and debugging. Each application has its own Unit output directory, so all of them are built independently.
  5. Dependency restrictions are enforced by search paths of projects.
  6. Parted build are created automatically (we use CruiseControl server and MSBuild project). Automatic build clears all temporary folders before build, so there are no dependencies between consecutive builds.

In our case, we could not control list of imported files. However, we could control a list of imported packages in parted builds. Smaller packages mean better granularity. If somebody is adding dependency to the unit, located in folder that is not available in search path, and package containing this unit is not in uses list, parted build is failed. So, explicit action (modifying MSBuild script that generates CFG files for parted build) is required to add dependency.

P.S. We use packages not to control dependencies, but because of Windows non-NT versions problems running large applications. So, dependency control is a side effect. Parted builds are considered as "release", and monolith - as "debug" configuration. Monolith applications are used only for coding and debugging. Developers work with monolith applications, and introduce their own changes to project configurations like attaching VCL debug info, switching on and off range check errors, optimization etc. However, after commit to SVN, CC tries to make parted build. It ignores CFG files from repository and re-creates them using special task of MSBuild project. So we can be sure no problems with dependencies were introduced (and perform other checks as well).

As far as we don't need monolith and parted builds simultaneously, we have only a single project per application. If you want to build both versions in MSBuild script, you could simply add one more target, re-create CFG one more time and specify one more Unit output directory. Naturally, if both versions are required for developers, it would be more convenient to have more projects.

Abelevich
Interesting; I appreciate your sharing this. Would it be fair to title your approach something like "Make extensive use of Packages"?
Mattias Andersson
When you write "monolith" and "parted builds", do you mean that you have several project files and that CC builds the "monolith" project and each "parted" project independently? And I presume you wrote your own MSBuild actions, from scratch, to generate the CFG files?
Mattias Andersson
See P.S. Yes, we're using custom MSBuild task to run Delphi compiler and specify project settings for it. And no, we haven't different projects for the same application because monolith and parted builds are required for different purposes, and developers need only monolith ones.
Abelevich
+1  A: 

I'm not sure if I understood question properly. Anyway, when building application suite (several projects but lot of common code), we create folder structure like this:

\Main
   \Project1
   \Project2
   ...
   \CommonUnits

We add common units to relevant projects (regardless it's not in same folder as project file). Further, sometimes it's easier to use project-level conditional defines (Project | Options | Directories/Conditionals) for small code differences. For example, Project1 will have something like "APP_PROJECT1" defined and you can then use $IFDEF in common units to write specific code.

What's important: in this case it's better to have one source control repository for whole suite (root is \Main, of course).

vrad