Question

I believe this question applies to CMSes in general.

I'm using a CMS, Kentico, for a particular web application. The CMS installer generates its own boilerplate project with 10,000+ files, and this project amounts to a runnable web app. Very few of the CMS-provided files are intended for modification. Yet standard practice is to add custom code to the project, and to check the entire project into source control.

I dislike the idea of checking in my entire CMS. I prefer my repository to contain only the code particular to the project, while vendor files are automatically "pulled in" from elsewhere. For example, Visual Studio can restore NuGet packages into a fixed location in the project, and this location can be ignored by version control.

In a way, development with the CMS is dirtier than when using a typical vendor library. Usually your own code depends on a library, while the library is independent from your code. However, the CMS wants to be your app, and your own code interweaves. The CMS requires customization of its own provided files. You can't just "pull in" the CMS files to a fixed location and then ignore that location.

Given this scenario, I'd still like to omit as many of the CMS files as possible. So far, I've settled on this strategy:

  1. Keep a pristine, readonly copy of the CMS-provided files at a standard, local path.
  2. Create a fresh project root directory.
  3. Copy the CMS-provided project file to the project root.
  4. In the copied project file, change the 10,000+ file paths to point to those pristine CMS files, which are external to the project root.
  5. Place only files with custom code into the project root. If a CMS-provided file must be modified, copy it into the project root directory and use that copy.

This strategy sounds good in theory, but it's more complicated in practice. Here are a couple complications:

  • Changing 10,000+ file paths to "link" in external files turns out to be more than a find-replace operation. More complex mapping is necessary to preserve the pristine project's directory structure.

  • The web server requires all the files to be in the same directory. Using the project file to link in external files is fine for compilation, but you've split your web root in two.

For the latter problem, it seems the best workaround is to perform post-build copy operations. This is, in itself, more complex than it seems from the outset.

As I write this, I'm starting to feel like I should go ahead and just dump the entire working CMS into my repository. It feels dirty, but it's a hell of a lot simpler. Before I do that, does anyone have any experience or ideas for how I might accomplish my goal?

To restate my question: How can I exclude my CMS's provided files from my version control repository, while still including them both in my project / build script and in my web app root?

Was it helpful?

Solution 3

I came up with an ideal strategy:

  • Place the pristine CMS files at an external "code libraries" location, and make them read-only for good measure.
  • Make a copy of the CMS-supplied project file.
  • Convert all project items to link to the code library's copy, including assembly references. The required link element for MSBuild complicates this, so I wrote a little console app to parse the XML and generate the necessary elements. I also store the path to the library in a property.

At this point, you have a compilable project. The files are all just links to read-only, baseline, CMS-provided files, but the IDE shows a nice directory structure, as if the files were actually in your repository. As you customize, you can replace the links with your own actual files.

However, your website files are now split between two completely different locations. The web server needs them to actually be in the same directory. This is where some scripting comes in handy.

  • Using your build scripting tool, plug into the post-build event to copy linked files into the project directory. Naturally, you can omit the compiled files. Here's how I did it with MSBuild (thanks to this blog post):

    <Target Name="AfterBuild">
    
        <ItemGroup>
            <LinkedKenticoFiles Include="@(None);@(EmbeddedResource);@(Content)"
                Condition="$([System.String]::new('%(FullPath)').StartsWith('$(MY_LIBRARY_BASE_PATH)'))" />
        </ItemGroup>
    
        <Copy SourceFiles="%(LinkedKenticoFiles.Identity)"
            DestinationFiles="%(LinkedKenticoFiles.Link)"
            SkipUnchangedFiles='true' />
    
    </Target>
    
  • Optionally, plug into the cleaning event to remove those files. I used similar MSBuild syntax to delete the files, and because it leaves behind empty directories, I remove empty directories, too. (Thanks to this answer.)

    <Target Name="BeforeClean">
    
        <ItemGroup>
            <LinkedKenticoFiles Include="@(None);@(EmbeddedResource);@(Content)"
                Condition="$([System.String]::new('%(FullPath)').StartsWith('$(MY_LIBRARY_BASE_PATH)'))" />
        </ItemGroup>
    
        <Delete Files="%(LinkedKenticoFiles.Link)" />
    
        <ItemGroup>
            <Directories Include="$([System.IO.Directory]::GetDirectories($(MSBuildProjectDirectory), '*', System.IO.SearchOption.AllDirectories))" />
            <Directories>
                <Files>$([System.IO.Directory]::GetFiles("%(Directories.Identity)", "*", System.IO.SearchOption.AllDirectories).get_Length())</Files>
            </Directories>
        </ItemGroup>
        <RemoveDir Directories="@(Directories)" Condition="%(Files)=='0'" />
    
    </Target>
    
  • Optionally, make your source control ignore the site / project folder, to avoid checking in the temporary, copied site files.

OTHER TIPS

Here's another idea for how to accomplish this:

  • I establish a standard local directory for Kentico DLLs and the Kentico installer, which will not be in version control.
  • I create KenticoSupport.csproj, a class library of types extending built-in Kentico types, just as they are intended to be extended. This will also contain content, such as JS, CSS, ASPX, ASCX, etc. files. This will be in version control, but it references the central Kentico DLLs, which are not in version control.
  • I create KenticoBuilder.csproj, a mostly empty project that is mainly a build script. It also contains a few simple *.patch files to modify matching Kentico files, such as Web.config and Global.asax.cs. This is under version control.

Here's what the KenticoBuilder.csproj script does:

  • Build KenticoSupport.csproj.
  • Create an output directory that will serve as both a web root and a project directory.
  • Install / copy the pristine Kentico project files into the output directory.
  • Apply patches to matching Kentico files in the output directory (mainly to add config lines and custom bootstrapping).
  • Copy custom DLLs and content from KenticoSupport to matching locations in the output directory.
  • Build the project at the output directory.

It's complex, but I think it might just work. Thoughts, anyone?

Kentico should hopefully be fixing this soon, with pre-built dlls you can reference rather than rebuilding the entire CMS every time you modify C#.

Until then, have you considered using a Symbolic Links to reference your pristine files? Then just overriding the links with your modified files when need be?

See this other question if you haven't used them before. mklink comes with newer Windows versions (Command-line), but there are a bunch of third party GUI based apps as well.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top